1 /* 2 * PROJECT: ReactOS Notepad 3 * LICENSE: LGPL-2.1-or-later (https://spdx.org/licenses/LGPL-2.1-or-later) 4 * PURPOSE: Providing a Windows-compatible simple text editor for ReactOS 5 * COPYRIGHT: Copyright 2000 Mike McCormack <Mike_McCormack@looksmart.com.au> 6 * Copyright 1997,98 Marcel Baur <mbaur@g26.ethz.ch> 7 * Copyright 2002 Sylvain Petreolle <spetreolle@yahoo.fr> 8 * Copyright 2002 Andriy Palamarchuk 9 * Copyright 2020-2023 Katayama Hirofumi MZ 10 */ 11 12 #include "notepad.h" 13 14 #include <shlobj.h> 15 #include <strsafe.h> 16 17 #ifdef _DEBUG 18 #include <crtdbg.h> 19 #endif 20 21 NOTEPAD_GLOBALS Globals; 22 static ATOM aFINDMSGSTRING; 23 24 VOID NOTEPAD_EnableSearchMenu() 25 { 26 BOOL bEmpty = (GetWindowTextLengthW(Globals.hEdit) == 0); 27 UINT uEnable = MF_BYCOMMAND | (bEmpty ? MF_GRAYED : MF_ENABLED); 28 EnableMenuItem(Globals.hMenu, CMD_SEARCH, uEnable); 29 EnableMenuItem(Globals.hMenu, CMD_SEARCH_NEXT, uEnable); 30 EnableMenuItem(Globals.hMenu, CMD_SEARCH_PREV, uEnable); 31 } 32 33 /*********************************************************************** 34 * SetFileName 35 * 36 * Sets Global File Name. 37 */ 38 VOID SetFileName(LPCTSTR szFileName) 39 { 40 StringCchCopy(Globals.szFileName, ARRAY_SIZE(Globals.szFileName), szFileName); 41 Globals.szFileTitle[0] = 0; 42 GetFileTitle(szFileName, Globals.szFileTitle, ARRAY_SIZE(Globals.szFileTitle)); 43 44 if (szFileName && szFileName[0]) 45 SHAddToRecentDocs(SHARD_PATHW, szFileName); 46 } 47 48 /*********************************************************************** 49 * NOTEPAD_MenuCommand 50 * 51 * All handling of main menu events 52 */ 53 static int NOTEPAD_MenuCommand(WPARAM wParam) 54 { 55 switch (wParam) 56 { 57 case CMD_NEW: DIALOG_FileNew(); break; 58 case CMD_NEW_WINDOW: DIALOG_FileNewWindow(); break; 59 case CMD_OPEN: DIALOG_FileOpen(); break; 60 case CMD_SAVE: DIALOG_FileSave(); break; 61 case CMD_SAVE_AS: DIALOG_FileSaveAs(); break; 62 case CMD_PRINT: DIALOG_FilePrint(); break; 63 case CMD_PAGE_SETUP: DIALOG_FilePageSetup(); break; 64 case CMD_EXIT: DIALOG_FileExit(); break; 65 66 case CMD_UNDO: DIALOG_EditUndo(); break; 67 case CMD_CUT: DIALOG_EditCut(); break; 68 case CMD_COPY: DIALOG_EditCopy(); break; 69 case CMD_PASTE: DIALOG_EditPaste(); break; 70 case CMD_DELETE: DIALOG_EditDelete(); break; 71 case CMD_SELECT_ALL: DIALOG_EditSelectAll(); break; 72 case CMD_TIME_DATE: DIALOG_EditTimeDate(); break; 73 74 case CMD_SEARCH: DIALOG_Search(); break; 75 case CMD_SEARCH_NEXT: DIALOG_SearchNext(TRUE); break; 76 case CMD_REPLACE: DIALOG_Replace(); break; 77 case CMD_GOTO: DIALOG_GoTo(); break; 78 case CMD_SEARCH_PREV: DIALOG_SearchNext(FALSE); break; 79 80 case CMD_WRAP: DIALOG_EditWrap(); break; 81 case CMD_FONT: DIALOG_SelectFont(); break; 82 83 case CMD_STATUSBAR: DIALOG_ViewStatusBar(); break; 84 85 case CMD_HELP_CONTENTS: DIALOG_HelpContents(); break; 86 case CMD_HELP_ABOUT_NOTEPAD: DIALOG_HelpAboutNotepad(); break; 87 88 default: 89 break; 90 } 91 return 0; 92 } 93 94 /*********************************************************************** 95 * NOTEPAD_FindTextAt 96 */ 97 static BOOL 98 NOTEPAD_FindTextAt(FINDREPLACE *pFindReplace, LPCTSTR pszText, INT iTextLength, DWORD dwPosition) 99 { 100 BOOL bMatches; 101 size_t iTargetLength; 102 LPCTSTR pchPosition; 103 104 if (!pFindReplace || !pszText) 105 return FALSE; 106 107 iTargetLength = _tcslen(pFindReplace->lpstrFindWhat); 108 pchPosition = &pszText[dwPosition]; 109 110 /* Make proper comparison */ 111 if (pFindReplace->Flags & FR_MATCHCASE) 112 bMatches = !_tcsncmp(pchPosition, pFindReplace->lpstrFindWhat, iTargetLength); 113 else 114 bMatches = !_tcsnicmp(pchPosition, pFindReplace->lpstrFindWhat, iTargetLength); 115 116 if (bMatches && (pFindReplace->Flags & FR_WHOLEWORD)) 117 { 118 if (dwPosition > 0) 119 { 120 if (_istalnum(*(pchPosition - 1)) || *(pchPosition - 1) == _T('_')) 121 bMatches = FALSE; 122 } 123 if ((INT)dwPosition + iTargetLength < iTextLength) 124 { 125 if (_istalnum(pchPosition[iTargetLength]) || pchPosition[iTargetLength] == _T('_')) 126 bMatches = FALSE; 127 } 128 } 129 130 return bMatches; 131 } 132 133 /*********************************************************************** 134 * NOTEPAD_FindNext 135 */ 136 BOOL NOTEPAD_FindNext(FINDREPLACE *pFindReplace, BOOL bReplace, BOOL bShowAlert) 137 { 138 int iTextLength, iTargetLength; 139 size_t iAdjustment = 0; 140 LPTSTR pszText = NULL; 141 DWORD dwPosition, dwBegin, dwEnd; 142 BOOL bMatches = FALSE; 143 TCHAR szResource[128], szText[128]; 144 BOOL bSuccess; 145 146 iTargetLength = (int) _tcslen(pFindReplace->lpstrFindWhat); 147 148 /* Retrieve the window text */ 149 iTextLength = GetWindowTextLength(Globals.hEdit); 150 if (iTextLength > 0) 151 { 152 pszText = (LPTSTR) HeapAlloc(GetProcessHeap(), 0, (iTextLength + 1) * sizeof(TCHAR)); 153 if (!pszText) 154 return FALSE; 155 156 GetWindowText(Globals.hEdit, pszText, iTextLength + 1); 157 } 158 159 SendMessage(Globals.hEdit, EM_GETSEL, (WPARAM) &dwBegin, (LPARAM) &dwEnd); 160 if (bReplace && ((dwEnd - dwBegin) == (DWORD) iTargetLength)) 161 { 162 if (NOTEPAD_FindTextAt(pFindReplace, pszText, iTextLength, dwBegin)) 163 { 164 SendMessage(Globals.hEdit, EM_REPLACESEL, TRUE, (LPARAM) pFindReplace->lpstrReplaceWith); 165 iAdjustment = _tcslen(pFindReplace->lpstrReplaceWith) - (dwEnd - dwBegin); 166 } 167 } 168 169 if (pFindReplace->Flags & FR_DOWN) 170 { 171 /* Find Down */ 172 dwPosition = dwEnd; 173 while(dwPosition < (DWORD) iTextLength) 174 { 175 bMatches = NOTEPAD_FindTextAt(pFindReplace, pszText, iTextLength, dwPosition); 176 if (bMatches) 177 break; 178 dwPosition++; 179 } 180 } 181 else 182 { 183 /* Find Up */ 184 dwPosition = dwBegin; 185 while(dwPosition > 0) 186 { 187 dwPosition--; 188 bMatches = NOTEPAD_FindTextAt(pFindReplace, pszText, iTextLength, dwPosition); 189 if (bMatches) 190 break; 191 } 192 } 193 194 if (bMatches) 195 { 196 /* Found target */ 197 if (dwPosition > dwBegin) 198 dwPosition += (DWORD) iAdjustment; 199 SendMessage(Globals.hEdit, EM_SETSEL, dwPosition, dwPosition + iTargetLength); 200 SendMessage(Globals.hEdit, EM_SCROLLCARET, 0, 0); 201 bSuccess = TRUE; 202 } 203 else 204 { 205 /* Can't find target */ 206 if (bShowAlert) 207 { 208 LoadString(Globals.hInstance, STRING_CANNOTFIND, szResource, ARRAY_SIZE(szResource)); 209 _sntprintf(szText, ARRAY_SIZE(szText), szResource, pFindReplace->lpstrFindWhat); 210 LoadString(Globals.hInstance, STRING_NOTEPAD, szResource, ARRAY_SIZE(szResource)); 211 MessageBox(Globals.hFindReplaceDlg, szText, szResource, MB_OK); 212 } 213 bSuccess = FALSE; 214 } 215 216 if (pszText) 217 HeapFree(GetProcessHeap(), 0, pszText); 218 return bSuccess; 219 } 220 221 /*********************************************************************** 222 * NOTEPAD_ReplaceAll 223 */ 224 static VOID NOTEPAD_ReplaceAll(FINDREPLACE *pFindReplace) 225 { 226 BOOL bShowAlert = TRUE; 227 228 SendMessage(Globals.hEdit, EM_SETSEL, 0, 0); 229 230 while (NOTEPAD_FindNext(pFindReplace, TRUE, bShowAlert)) 231 { 232 bShowAlert = FALSE; 233 } 234 } 235 236 /*********************************************************************** 237 * NOTEPAD_FindTerm 238 */ 239 static VOID NOTEPAD_FindTerm(VOID) 240 { 241 Globals.hFindReplaceDlg = NULL; 242 } 243 244 /*********************************************************************** 245 * Data Initialization 246 */ 247 static VOID NOTEPAD_InitData(HINSTANCE hInstance) 248 { 249 LPTSTR p; 250 static const TCHAR txt_files[] = _T("*.txt"); 251 static const TCHAR all_files[] = _T("*.*"); 252 253 ZeroMemory(&Globals, sizeof(Globals)); 254 Globals.hInstance = hInstance; 255 Globals.encFile = ENCODING_DEFAULT; 256 257 p = Globals.szFilter; 258 p += LoadString(Globals.hInstance, STRING_TEXT_FILES_TXT, p, MAX_STRING_LEN) + 1; 259 _tcscpy(p, txt_files); 260 p += ARRAY_SIZE(txt_files); 261 262 p += LoadString(Globals.hInstance, STRING_ALL_FILES, p, MAX_STRING_LEN) + 1; 263 _tcscpy(p, all_files); 264 p += ARRAY_SIZE(all_files); 265 *p = '\0'; 266 Globals.find.lpstrFindWhat = NULL; 267 268 Globals.hDevMode = NULL; 269 Globals.hDevNames = NULL; 270 } 271 272 /*********************************************************************** 273 * Enable/disable items on the menu based on control state 274 */ 275 static VOID NOTEPAD_InitMenuPopup(HMENU menu, LPARAM index) 276 { 277 int enable; 278 279 UNREFERENCED_PARAMETER(index); 280 281 CheckMenuItem(menu, CMD_WRAP, (Globals.bWrapLongLines ? MF_CHECKED : MF_UNCHECKED)); 282 CheckMenuItem(menu, CMD_STATUSBAR, (Globals.bShowStatusBar ? MF_CHECKED : MF_UNCHECKED)); 283 EnableMenuItem(menu, CMD_UNDO, 284 SendMessage(Globals.hEdit, EM_CANUNDO, 0, 0) ? MF_ENABLED : MF_GRAYED); 285 EnableMenuItem(menu, CMD_PASTE, 286 IsClipboardFormatAvailable(CF_TEXT) ? MF_ENABLED : MF_GRAYED); 287 enable = (int) SendMessage(Globals.hEdit, EM_GETSEL, 0, 0); 288 enable = (HIWORD(enable) == LOWORD(enable)) ? MF_GRAYED : MF_ENABLED; 289 EnableMenuItem(menu, CMD_CUT, enable); 290 EnableMenuItem(menu, CMD_COPY, enable); 291 EnableMenuItem(menu, CMD_DELETE, enable); 292 293 EnableMenuItem(menu, CMD_SELECT_ALL, 294 GetWindowTextLength(Globals.hEdit) ? MF_ENABLED : MF_GRAYED); 295 } 296 297 LRESULT CALLBACK EDIT_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) 298 { 299 switch (msg) 300 { 301 case WM_KEYDOWN: 302 case WM_KEYUP: 303 { 304 switch (wParam) 305 { 306 case VK_UP: 307 case VK_DOWN: 308 case VK_LEFT: 309 case VK_RIGHT: 310 DIALOG_StatusBarUpdateCaretPos(); 311 break; 312 default: 313 { 314 UpdateWindowCaption(FALSE); 315 break; 316 } 317 } 318 } 319 case WM_LBUTTONUP: 320 { 321 DIALOG_StatusBarUpdateCaretPos(); 322 break; 323 } 324 } 325 return CallWindowProc( Globals.EditProc, hWnd, msg, wParam, lParam); 326 } 327 328 /*********************************************************************** 329 * NOTEPAD_WndProc 330 */ 331 static LRESULT 332 WINAPI 333 NOTEPAD_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) 334 { 335 switch (msg) 336 { 337 338 case WM_CREATE: 339 Globals.hMainWnd = hWnd; 340 Globals.hMenu = GetMenu(hWnd); 341 342 DragAcceptFiles(hWnd, TRUE); /* Accept Drag & Drop */ 343 344 /* Create controls */ 345 DoCreateEditWindow(); 346 DoShowHideStatusBar(); 347 348 DIALOG_FileNew(); /* Initialize file info */ 349 350 // For now, the "Help" dialog is disabled due to the lack of HTML Help support 351 EnableMenuItem(Globals.hMenu, CMD_HELP_CONTENTS, MF_BYCOMMAND | MF_GRAYED); 352 break; 353 354 case WM_COMMAND: 355 if (HIWORD(wParam) == EN_CHANGE || HIWORD(wParam) == EN_HSCROLL || HIWORD(wParam) == EN_VSCROLL) 356 DIALOG_StatusBarUpdateCaretPos(); 357 if ((HIWORD(wParam) == EN_CHANGE)) 358 NOTEPAD_EnableSearchMenu(); 359 NOTEPAD_MenuCommand(LOWORD(wParam)); 360 break; 361 362 case WM_CLOSE: 363 if (DoCloseFile()) 364 DestroyWindow(hWnd); 365 break; 366 367 case WM_QUERYENDSESSION: 368 if (DoCloseFile()) { 369 return 1; 370 } 371 break; 372 373 case WM_DESTROY: 374 if (Globals.hFont) 375 DeleteObject(Globals.hFont); 376 if (Globals.hDevMode) 377 GlobalFree(Globals.hDevMode); 378 if (Globals.hDevNames) 379 GlobalFree(Globals.hDevNames); 380 SetWindowLongPtr(Globals.hEdit, GWLP_WNDPROC, (LONG_PTR)Globals.EditProc); 381 NOTEPAD_SaveSettingsToRegistry(); 382 PostQuitMessage(0); 383 break; 384 385 case WM_SIZE: 386 { 387 RECT rc; 388 GetClientRect(hWnd, &rc); 389 390 if (Globals.bShowStatusBar) 391 { 392 RECT rcStatus; 393 SendMessageW(Globals.hStatusBar, WM_SIZE, 0, 0); 394 GetWindowRect(Globals.hStatusBar, &rcStatus); 395 rc.bottom -= rcStatus.bottom - rcStatus.top; 396 } 397 398 MoveWindow(Globals.hEdit, 0, 0, rc.right, rc.bottom, TRUE); 399 400 if (Globals.bShowStatusBar) 401 { 402 /* Align status bar parts, only if the status bar resize operation succeeds */ 403 DIALOG_StatusBarAlignParts(); 404 } 405 break; 406 } 407 408 /* The entire client area is covered by edit control and by 409 * the status bar. So there is no need to erase main background. 410 * This resolves the horrible flicker effect during windows resizes. */ 411 case WM_ERASEBKGND: 412 return 1; 413 414 case WM_SETFOCUS: 415 SetFocus(Globals.hEdit); 416 break; 417 418 case WM_DROPFILES: 419 { 420 TCHAR szFileName[MAX_PATH]; 421 HDROP hDrop = (HDROP) wParam; 422 423 DragQueryFile(hDrop, 0, szFileName, ARRAY_SIZE(szFileName)); 424 DragFinish(hDrop); 425 DoOpenFile(szFileName); 426 break; 427 } 428 429 case WM_INITMENUPOPUP: 430 NOTEPAD_InitMenuPopup((HMENU)wParam, lParam); 431 break; 432 433 default: 434 if (msg == aFINDMSGSTRING) 435 { 436 FINDREPLACE *pFindReplace = (FINDREPLACE *) lParam; 437 Globals.find = *(FINDREPLACE *) lParam; 438 439 if (pFindReplace->Flags & FR_FINDNEXT) 440 NOTEPAD_FindNext(pFindReplace, FALSE, TRUE); 441 else if (pFindReplace->Flags & FR_REPLACE) 442 NOTEPAD_FindNext(pFindReplace, TRUE, TRUE); 443 else if (pFindReplace->Flags & FR_REPLACEALL) 444 NOTEPAD_ReplaceAll(pFindReplace); 445 else if (pFindReplace->Flags & FR_DIALOGTERM) 446 NOTEPAD_FindTerm(); 447 break; 448 } 449 450 return DefWindowProc(hWnd, msg, wParam, lParam); 451 } 452 return 0; 453 } 454 455 static int AlertFileDoesNotExist(LPCTSTR szFileName) 456 { 457 return DIALOG_StringMsgBox(Globals.hMainWnd, STRING_DOESNOTEXIST, 458 szFileName, 459 MB_ICONEXCLAMATION | MB_YESNO); 460 } 461 462 static BOOL HandleCommandLine(LPTSTR cmdline) 463 { 464 BOOL opt_print = FALSE; 465 TCHAR szPath[MAX_PATH]; 466 467 while (*cmdline == _T(' ') || *cmdline == _T('-') || *cmdline == _T('/')) 468 { 469 TCHAR option; 470 471 if (*cmdline++ == _T(' ')) continue; 472 473 option = *cmdline; 474 if (option) cmdline++; 475 while (*cmdline == _T(' ')) cmdline++; 476 477 switch(option) 478 { 479 case 'p': 480 case 'P': 481 opt_print = TRUE; 482 break; 483 } 484 } 485 486 if (*cmdline) 487 { 488 /* file name is passed in the command line */ 489 LPCTSTR file_name = NULL; 490 BOOL file_exists = FALSE; 491 TCHAR buf[MAX_PATH]; 492 493 if (cmdline[0] == _T('"')) 494 { 495 cmdline++; 496 cmdline[lstrlen(cmdline) - 1] = 0; 497 } 498 499 file_name = cmdline; 500 if (FileExists(file_name)) 501 { 502 file_exists = TRUE; 503 } 504 else if (!HasFileExtension(cmdline)) 505 { 506 static const TCHAR txt[] = _T(".txt"); 507 508 /* try to find file with ".txt" extension */ 509 if (!_tcscmp(txt, cmdline + _tcslen(cmdline) - _tcslen(txt))) 510 { 511 file_exists = FALSE; 512 } 513 else 514 { 515 _tcsncpy(buf, cmdline, MAX_PATH - _tcslen(txt) - 1); 516 _tcscat(buf, txt); 517 file_name = buf; 518 file_exists = FileExists(file_name); 519 } 520 } 521 522 GetFullPathName(file_name, ARRAY_SIZE(szPath), szPath, NULL); 523 524 if (file_exists) 525 { 526 DoOpenFile(szPath); 527 InvalidateRect(Globals.hMainWnd, NULL, FALSE); 528 if (opt_print) 529 { 530 DIALOG_FilePrint(); 531 return FALSE; 532 } 533 } 534 else 535 { 536 switch (AlertFileDoesNotExist(file_name)) 537 { 538 case IDYES: 539 DoOpenFile(szPath); 540 break; 541 542 case IDNO: 543 break; 544 } 545 } 546 } 547 548 return TRUE; 549 } 550 551 /*********************************************************************** 552 * WinMain 553 */ 554 int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE prev, LPTSTR cmdline, int show) 555 { 556 MSG msg; 557 HACCEL hAccel; 558 WNDCLASSEX wndclass; 559 HMONITOR monitor; 560 MONITORINFO info; 561 INT x, y; 562 RECT rcIntersect; 563 static const TCHAR className[] = _T("Notepad"); 564 static const TCHAR winName[] = _T("Notepad"); 565 566 #ifdef _DEBUG 567 /* Report any memory leaks on exit */ 568 _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); 569 #endif 570 571 switch (GetUserDefaultUILanguage()) 572 { 573 case MAKELANGID(LANG_HEBREW, SUBLANG_DEFAULT): 574 SetProcessDefaultLayout(LAYOUT_RTL); 575 break; 576 577 default: 578 break; 579 } 580 581 UNREFERENCED_PARAMETER(prev); 582 583 aFINDMSGSTRING = (ATOM)RegisterWindowMessage(FINDMSGSTRING); 584 585 NOTEPAD_InitData(hInstance); 586 NOTEPAD_LoadSettingsFromRegistry(); 587 588 ZeroMemory(&wndclass, sizeof(wndclass)); 589 wndclass.cbSize = sizeof(wndclass); 590 wndclass.lpfnWndProc = NOTEPAD_WndProc; 591 wndclass.hInstance = Globals.hInstance; 592 wndclass.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_NPICON)); 593 wndclass.hCursor = LoadCursor(0, IDC_ARROW); 594 wndclass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); 595 wndclass.lpszMenuName = MAKEINTRESOURCE(MAIN_MENU); 596 wndclass.lpszClassName = className; 597 wndclass.hIconSm = (HICON)LoadImage(hInstance, 598 MAKEINTRESOURCE(IDI_NPICON), 599 IMAGE_ICON, 600 GetSystemMetrics(SM_CXSMICON), 601 GetSystemMetrics(SM_CYSMICON), 602 0); 603 if (!RegisterClassEx(&wndclass)) 604 { 605 ShowLastError(); 606 return 1; 607 } 608 609 /* Setup windows */ 610 611 monitor = MonitorFromRect(&Globals.main_rect, MONITOR_DEFAULTTOPRIMARY); 612 info.cbSize = sizeof(info); 613 GetMonitorInfoW(monitor, &info); 614 615 x = Globals.main_rect.left; 616 y = Globals.main_rect.top; 617 if (!IntersectRect(&rcIntersect, &Globals.main_rect, &info.rcWork)) 618 x = y = CW_USEDEFAULT; 619 620 /* Globals.hMainWnd will be set in WM_CREATE handling */ 621 CreateWindow(className, 622 winName, 623 WS_OVERLAPPEDWINDOW, 624 x, 625 y, 626 Globals.main_rect.right - Globals.main_rect.left, 627 Globals.main_rect.bottom - Globals.main_rect.top, 628 NULL, 629 NULL, 630 Globals.hInstance, 631 NULL); 632 if (!Globals.hMainWnd) 633 { 634 ShowLastError(); 635 return 1; 636 } 637 638 ShowWindow(Globals.hMainWnd, show); 639 UpdateWindow(Globals.hMainWnd); 640 641 if (!HandleCommandLine(cmdline)) 642 return 0; 643 644 hAccel = LoadAccelerators(hInstance, MAKEINTRESOURCE(ID_ACCEL)); 645 646 while (GetMessage(&msg, NULL, 0, 0)) 647 { 648 if (!TranslateAccelerator(Globals.hMainWnd, hAccel, &msg) && 649 !IsDialogMessage(Globals.hFindReplaceDlg, &msg)) 650 { 651 TranslateMessage(&msg); 652 DispatchMessage(&msg); 653 } 654 } 655 656 DestroyAcceleratorTable(hAccel); 657 658 return (int) msg.wParam; 659 } 660