#define UNICODE #define _UNICODE #define __STDC_WANT_LIB_EXT1__ 1 #include #include #include #include #include #include #include "resource.h" #ifndef swprintf_s int swprintf_s(wchar_t *buffer, size_t sizeOfBuffer, const wchar_t *format, ...); #endif #define IDM_FILE_NEW 1001 #define IDM_FILE_OPEN 1002 #define IDM_FILE_SAVE 1003 #define IDM_FILE_SAVE_AS 1004 #define IDM_FILE_EXIT 1005 #define IDM_EDIT_UNDO 2001 #define IDM_EDIT_CUT 2002 #define IDM_EDIT_COPY 2003 #define IDM_EDIT_PASTE 2004 #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 #define IDM_HELP_ABOUT 4001 static const WCHAR APP_VERSION[] = L"1.1.0"; static HWND g_hEdit = NULL; static HFONT g_hFont = NULL; static HMENU g_hMenu = NULL; 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])) #endif int swprintf_s(wchar_t *buffer, size_t sizeOfBuffer, const wchar_t *format, ...); static void CopyStringSafe(WCHAR *dest, size_t destCount, const WCHAR *src) { if (!dest || destCount == 0) { return; } if (!src) { dest[0] = L'\0'; return; } wcsncpy(dest, src, destCount - 1); dest[destCount - 1] = L'\0'; } static void UpdateTitle(HWND hwnd) { const WCHAR *name = g_filePath; const WCHAR *lastSlash = wcsrchr(g_filePath, L'\\'); if (lastSlash && *(lastSlash + 1) != L'\0') { name = lastSlash + 1; } if (name[0] == L'\0') { name = L"Untitled"; } WCHAR title[512]; swprintf_s(title, ARRAYSIZE(title), L"%s - ClassicNotepad", name); SetWindowTextW(hwnd, title); } static void SetDefaultFont(HWND hwnd) { if (!g_hFont) { if (!SystemParametersInfoW(SPI_GETICONTITLELOGFONT, sizeof(g_logFont), &g_logFont, 0)) { ZeroMemory(&g_logFont, sizeof(g_logFont)); g_logFont.lfHeight = -11; wcscpy(g_logFont.lfFaceName, L"Segoe UI"); } g_hFont = CreateFontIndirectW(&g_logFont); } SendMessage(hwnd, WM_SETFONT, (WPARAM)g_hFont, TRUE); } static void ResizeEdit(HWND hwndParent) { RECT rc; GetClientRect(hwndParent, &rc); MoveWindow(g_hEdit, 0, 0, rc.right - rc.left, rc.bottom - rc.top, TRUE); } static void RecreateEdit(HWND hwndParent) { int len = GetWindowTextLengthW(g_hEdit); WCHAR *buffer = (WCHAR *)HeapAlloc(GetProcessHeap(), 0, (len + 1) * sizeof(WCHAR)); if (buffer) { GetWindowTextW(g_hEdit, buffer, len + 1); } DestroyWindow(g_hEdit); DWORD styles = WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_TABSTOP | ES_LEFT | ES_MULTILINE | ES_AUTOVSCROLL | ES_WANTRETURN; if (!g_wordWrap) { styles |= WS_HSCROLL | ES_AUTOHSCROLL; } g_hEdit = CreateWindowExW(0, L"EDIT", NULL, styles, 0, 0, 0, 0, hwndParent, (HMENU)1, GetModuleHandle(NULL), NULL); SetDefaultFont(g_hEdit); if (buffer) { SetWindowTextW(g_hEdit, buffer); SendMessageW(g_hEdit, EM_SETSEL, (WPARAM)len, (LPARAM)len); HeapFree(GetProcessHeap(), 0, buffer); } ResizeEdit(hwndParent); } static HMENU BuildMenu(void) { HMENU hMenu = CreateMenu(); HMENU hFile = CreatePopupMenu(); AppendMenuW(hFile, MF_STRING, IDM_FILE_NEW, L"&New\tCtrl+N"); AppendMenuW(hFile, MF_STRING, IDM_FILE_OPEN, L"&Open...\tCtrl+O"); AppendMenuW(hFile, MF_STRING, IDM_FILE_SAVE, L"&Save\tCtrl+S"); AppendMenuW(hFile, MF_STRING, IDM_FILE_SAVE_AS, L"Save &As..."); AppendMenuW(hFile, MF_SEPARATOR, 0, NULL); AppendMenuW(hFile, MF_STRING, IDM_FILE_EXIT, L"E&xit"); HMENU hEdit = CreatePopupMenu(); AppendMenuW(hEdit, MF_STRING, IDM_EDIT_UNDO, L"&Undo\tCtrl+Z"); AppendMenuW(hEdit, MF_SEPARATOR, 0, NULL); AppendMenuW(hEdit, MF_STRING, IDM_EDIT_CUT, L"Cu&t\tCtrl+X"); AppendMenuW(hEdit, MF_STRING, IDM_EDIT_COPY, L"&Copy\tCtrl+C"); 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"); HMENU hFormat = CreatePopupMenu(); AppendMenuW(hFormat, MF_STRING | (g_wordWrap ? MF_CHECKED : 0), IDM_FORMAT_WRAP, L"&Word Wrap"); AppendMenuW(hFormat, MF_STRING, IDM_FORMAT_FONT, L"&Font..."); HMENU hHelp = CreatePopupMenu(); AppendMenuW(hHelp, MF_STRING, IDM_HELP_ABOUT, L"&About ClassicNotepad"); AppendMenuW(hMenu, MF_POPUP, (UINT_PTR)hFile, L"&File"); AppendMenuW(hMenu, MF_POPUP, (UINT_PTR)hEdit, L"&Edit"); AppendMenuW(hMenu, MF_POPUP, (UINT_PTR)hFormat, L"F&ormat"); AppendMenuW(hMenu, MF_POPUP, (UINT_PTR)hHelp, L"&Help"); return hMenu; } static void UpdateWrapMenu(void) { HMENU hFormat = GetSubMenu(g_hMenu, 2); CheckMenuItem(hFormat, IDM_FORMAT_WRAP, MF_BYCOMMAND | (g_wordWrap ? MF_CHECKED : MF_UNCHECKED)); } static bool ShowOpenDialog(HWND hwnd, WCHAR *pathBuffer, DWORD cchBuffer) { pathBuffer[0] = L'\0'; OPENFILENAMEW ofn = {0}; ofn.lStructSize = sizeof(ofn); ofn.hwndOwner = hwnd; ofn.lpstrFilter = L"Text Files (*.txt)\0*.txt\0All Files (*.*)\0*.*\0"; ofn.lpstrFile = pathBuffer; ofn.nMaxFile = cchBuffer; ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_EXPLORER; ofn.lpstrDefExt = L"txt"; return GetOpenFileNameW(&ofn); } static bool ShowSaveDialog(HWND hwnd) { OPENFILENAMEW ofn = {0}; ofn.lStructSize = sizeof(ofn); ofn.hwndOwner = hwnd; ofn.lpstrFilter = L"Text Files (*.txt)\0*.txt\0All Files (*.*)\0*.*\0"; ofn.lpstrFile = g_filePath; ofn.nMaxFile = MAX_PATH; ofn.Flags = OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST | OFN_EXPLORER; ofn.lpstrDefExt = L"txt"; return GetSaveFileNameW(&ofn); } static bool LoadFileToEdit(HWND hwnd, const WCHAR *path) { HANDLE hFile = CreateFileW(path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { MessageBoxW(hwnd, L"Unable to open the file.", L"Error", MB_ICONERROR | MB_OK); return false; } LARGE_INTEGER size; if (!GetFileSizeEx(hFile, &size) || size.QuadPart > 20 * 1024 * 1024) { CloseHandle(hFile); MessageBoxW(hwnd, L"File is too large to open.", L"Error", MB_ICONERROR | MB_OK); return false; } DWORD bytesToRead = (DWORD)size.QuadPart; BYTE *buffer = (BYTE *)HeapAlloc(GetProcessHeap(), 0, bytesToRead + 3); DWORD bytesRead = 0; bool success = false; if (buffer && ReadFile(hFile, buffer, bytesToRead, &bytesRead, NULL)) { int skip = 0; UINT codePage = CP_UTF8; if (bytesRead >= 3 && buffer[0] == 0xEF && buffer[1] == 0xBB && buffer[2] == 0xBF) { skip = 3; } else if (bytesRead >= 2 && buffer[0] == 0xFF && buffer[1] == 0xFE) { codePage = 1200; // UTF-16 LE } else if (bytesRead >= 2 && buffer[0] == 0xFE && buffer[1] == 0xFF) { codePage = 1201; // UTF-16 BE } else { codePage = CP_ACP; } int wideLen = MultiByteToWideChar(codePage, 0, (LPCCH)(buffer + skip), bytesRead - skip, NULL, 0); WCHAR *wbuf = (WCHAR *)HeapAlloc(GetProcessHeap(), 0, (wideLen + 1) * sizeof(WCHAR)); if (wbuf) { MultiByteToWideChar(codePage, 0, (LPCCH)(buffer + skip), bytesRead - skip, wbuf, wideLen); wbuf[wideLen] = L'\0'; SetWindowTextW(g_hEdit, wbuf); HeapFree(GetProcessHeap(), 0, wbuf); success = true; } } if (buffer) { HeapFree(GetProcessHeap(), 0, buffer); } CloseHandle(hFile); if (!success) { MessageBoxW(hwnd, L"Unable to read the file.", L"Error", MB_ICONERROR | MB_OK); } return success; } static bool SaveFileFromEdit(HWND hwnd, const WCHAR *path) { int len = GetWindowTextLengthW(g_hEdit); WCHAR *wbuf = (WCHAR *)HeapAlloc(GetProcessHeap(), 0, (len + 1) * sizeof(WCHAR)); if (!wbuf) { MessageBoxW(hwnd, L"Not enough memory to save.", L"Error", MB_ICONERROR | MB_OK); return false; } GetWindowTextW(g_hEdit, wbuf, len + 1); HANDLE hFile = CreateFileW(path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { HeapFree(GetProcessHeap(), 0, wbuf); MessageBoxW(hwnd, L"Unable to create the file.", L"Error", MB_ICONERROR | MB_OK); return false; } // Save as UTF-8 with BOM for reliable re-open. BYTE bom[] = {0xEF, 0xBB, 0xBF}; DWORD written = 0; bool success = false; if (!WriteFile(hFile, bom, sizeof(bom), &written, NULL)) { MessageBoxW(hwnd, L"Failed to save the file.", L"Error", MB_ICONERROR | MB_OK); HeapFree(GetProcessHeap(), 0, wbuf); CloseHandle(hFile); return false; } int utf8Len = WideCharToMultiByte(CP_UTF8, 0, wbuf, len, NULL, 0, NULL, NULL); if (utf8Len > 0) { BYTE *mbuf = (BYTE *)HeapAlloc(GetProcessHeap(), 0, utf8Len); if (mbuf) { WideCharToMultiByte(CP_UTF8, 0, wbuf, len, (LPSTR)mbuf, utf8Len, NULL, NULL); if (WriteFile(hFile, mbuf, utf8Len, &written, NULL)) { success = true; } HeapFree(GetProcessHeap(), 0, mbuf); } } else { success = true; // Empty file is fine. } HeapFree(GetProcessHeap(), 0, wbuf); CloseHandle(hFile); if (!success) { MessageBoxW(hwnd, L"Failed to save the file.", L"Error", MB_ICONERROR | MB_OK); } return success; } static void DoFileNew(HWND hwnd) { g_filePath[0] = L'\0'; SetWindowTextW(g_hEdit, L""); UpdateTitle(hwnd); } static void DoFileOpen(HWND hwnd) { WCHAR original[MAX_PATH]; CopyStringSafe(original, ARRAYSIZE(original), g_filePath); if (!ShowOpenDialog(hwnd, g_filePath, MAX_PATH)) { CopyStringSafe(g_filePath, ARRAYSIZE(g_filePath), original); return; } if (LoadFileToEdit(hwnd, g_filePath)) { UpdateTitle(hwnd); } else { CopyStringSafe(g_filePath, ARRAYSIZE(g_filePath), original); } } static void DoFileSave(HWND hwnd) { if (g_filePath[0] == L'\0') { if (!ShowSaveDialog(hwnd)) { return; } } if (SaveFileFromEdit(hwnd, g_filePath)) { UpdateTitle(hwnd); } } static void DoFileSaveAs(HWND hwnd) { WCHAR backup[MAX_PATH]; CopyStringSafe(backup, ARRAYSIZE(backup), g_filePath); if (ShowSaveDialog(hwnd)) { if (SaveFileFromEdit(hwnd, g_filePath)) { UpdateTitle(hwnd); } else { CopyStringSafe(g_filePath, ARRAYSIZE(g_filePath), backup); } } } static void InsertTimeDate(void) { SYSTEMTIME st; GetLocalTime(&st); WCHAR text[64]; 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) { g_wordWrap = !g_wordWrap; RecreateEdit(hwnd); UpdateWrapMenu(); } static void ChooseFontAndApply(HWND hwnd) { CHOOSEFONTW cf = {0}; cf.lStructSize = sizeof(cf); cf.hwndOwner = hwnd; cf.lpLogFont = &g_logFont; cf.Flags = CF_SCREENFONTS | CF_INITTOLOGFONTSTRUCT | CF_EFFECTS; if (ChooseFontW(&cf)) { if (g_hFont) { DeleteObject(g_hFont); g_hFont = NULL; } g_hFont = CreateFontIndirectW(&g_logFont); SetDefaultFont(g_hEdit); } } static void ShowAbout(HWND hwnd) { WCHAR message[256]; swprintf_s(message, ARRAYSIZE(message), L"ClassicNotepad v%s\nBuilt with Win32 API in C.\ngithub.com/acidburnmonkey/ClassicNotepad", APP_VERSION); MessageBoxW(hwnd, message, L"About", MB_OK | MB_ICONINFORMATION); } 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: g_hMenu = BuildMenu(); SetMenu(hwnd, g_hMenu); RecreateEdit(hwnd); UpdateTitle(hwnd); return 0; case WM_SIZE: ResizeEdit(hwnd); return 0; case WM_COMMAND: switch (LOWORD(wParam)) { case IDM_FILE_NEW: DoFileNew(hwnd); break; case IDM_FILE_OPEN: DoFileOpen(hwnd); break; case IDM_FILE_SAVE: DoFileSave(hwnd); break; case IDM_FILE_SAVE_AS: DoFileSaveAs(hwnd); break; case IDM_FILE_EXIT: PostMessageW(hwnd, WM_CLOSE, 0, 0); break; case IDM_EDIT_UNDO: SendMessageW(g_hEdit, WM_UNDO, 0, 0); break; case IDM_EDIT_CUT: SendMessageW(g_hEdit, WM_CUT, 0, 0); break; case IDM_EDIT_COPY: SendMessageW(g_hEdit, WM_COPY, 0, 0); break; case IDM_EDIT_PASTE: SendMessageW(g_hEdit, WM_PASTE, 0, 0); break; 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; case IDM_EDIT_TIME: InsertTimeDate(); break; case IDM_FORMAT_WRAP: ToggleWordWrap(hwnd); break; case IDM_FORMAT_FONT: ChooseFontAndApply(hwnd); break; case IDM_HELP_ABOUT: ShowAbout(hwnd); break; } return 0; case WM_SETFOCUS: SetFocus(g_hEdit); return 0; case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProcW(hwnd, msg, wParam, lParam); } int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow) { (void)hPrevInstance; (void)lpCmdLine; { HMODULE hUser = GetModuleHandleW(L"user32.dll"); if (hUser) { BOOL(WINAPI *pSetProcessDPIAware)(void) = (BOOL(WINAPI *)(void))GetProcAddress(hUser, "SetProcessDPIAware"); if (pSetProcessDPIAware) { pSetProcessDPIAware(); } } } g_msgFindReplace = RegisterWindowMessageW(FINDMSGSTRING); ACCEL accels[] = { {FVIRTKEY | FCONTROL, 'N', IDM_FILE_NEW}, {FVIRTKEY | FCONTROL, 'O', IDM_FILE_OPEN}, {FVIRTKEY | FCONTROL, 'S', IDM_FILE_SAVE}, {FVIRTKEY | FCONTROL, 'A', IDM_EDIT_SELECT}, {FVIRTKEY | FCONTROL, 'Z', IDM_EDIT_UNDO}, {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])); WNDCLASSEXW wc = {0}; wc.cbSize = sizeof(wc); wc.lpfnWndProc = WndProc; wc.hInstance = hInstance; 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); } if (!hIconSmall) { hIconSmall = LoadIcon(NULL, IDI_APPLICATION); } wc.hIcon = hIconLarge; wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wc.lpszClassName = L"ClassicNotepadClass"; wc.hIconSm = hIconSmall; if (!RegisterClassExW(&wc)) { MessageBoxW(NULL, L"Failed to register window class.", L"Error", MB_ICONERROR | MB_OK); 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, posX, posY, winWidth, winHeight, NULL, NULL, hInstance, NULL); if (!hwnd) { MessageBoxW(NULL, L"Failed to create window.", L"Error", MB_ICONERROR | MB_OK); return -1; } ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd); 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); DispatchMessageW(&msg); } } return (int)msg.wParam; } int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { (void)lpCmdLine; return wWinMain(hInstance, hPrevInstance, GetCommandLineW(), nCmdShow); }