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