#define UNICODE #define _UNICODE #define __STDC_WANT_LIB_EXT1__ 1 #include #include #include #include #include #ifndef swprintf_s int swprintf_s(wchar_t *buffer, size_t sizeOfBuffer, const wchar_t *format, ...); #endif #define IDI_APP 101 #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_FORMAT_WRAP 3001 #define IDM_FORMAT_FONT 3002 #define IDM_HELP_ABOUT 4001 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}; #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 - ClasicNotepad", 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_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 ClasicNotepad"); 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 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) { MessageBoxW(hwnd, L"ClasicNotepad\nBuilt with Win32 API in C.", L"About", MB_OK | MB_ICONINFORMATION); } static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { 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_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(); } } } 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, 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 = LoadIconW(GetModuleHandleW(NULL), MAKEINTRESOURCEW(IDI_APP)); HICON hIconSmall = LoadIconW(GetModuleHandleW(NULL), MAKEINTRESOURCEW(IDI_APP)); 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"ClasicNotepadClass"; wc.hIconSm = hIconSmall; if (!RegisterClassExW(&wc)) { MessageBoxW(NULL, L"Failed to register window class.", L"Error", MB_ICONERROR | MB_OK); return -1; } HWND hwnd = CreateWindowExW(0, wc.lpszClassName, L"ClasicNotepad", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 800, 600, 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 (!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); }