1081 lines
30 KiB
C
1081 lines
30 KiB
C
#define UNICODE
|
|
#define _UNICODE
|
|
#define __STDC_WANT_LIB_EXT1__ 1
|
|
#include <windows.h>
|
|
#include <commdlg.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <wchar.h>
|
|
#include <wctype.h>
|
|
#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.2.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;
|
|
|
|
{
|
|
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);
|
|
|
|
if (lpCmdLine && lpCmdLine[0])
|
|
{
|
|
int argc;
|
|
LPWSTR *argv = CommandLineToArgvW(lpCmdLine, &argc);
|
|
if (argv && argc >= 2)
|
|
{
|
|
LoadFileToEdit(hwnd, argv[1]);
|
|
}
|
|
if (argv) LocalFree(argv);
|
|
}
|
|
|
|
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);
|
|
}
|