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 1998,99 Marcel Baur <mbaur@g26.ethz.ch> 6 * Copyright 2002 Sylvain Petreolle <spetreolle@yahoo.fr> 7 * Copyright 2002 Andriy Palamarchuk 8 * Copyright 2023 Katayama Hirofumi MZ <katayama.hirofumi.mz@gmail.com> 9 */ 10 11 #include "notepad.h" 12 13 #include <assert.h> 14 #include <commctrl.h> 15 #include <strsafe.h> 16 17 LRESULT CALLBACK EDIT_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); 18 19 static const TCHAR helpfile[] = _T("notepad.hlp"); 20 static const TCHAR empty_str[] = _T(""); 21 static const TCHAR szDefaultExt[] = _T("txt"); 22 static const TCHAR txt_files[] = _T("*.txt"); 23 24 /* Status bar parts index */ 25 #define SBPART_CURPOS 0 26 #define SBPART_EOLN 1 27 #define SBPART_ENCODING 2 28 29 /* Line endings - string resource ID mapping table */ 30 static UINT EolnToStrId[] = { 31 STRING_CRLF, 32 STRING_LF, 33 STRING_CR 34 }; 35 36 /* Encoding - string resource ID mapping table */ 37 static UINT EncToStrId[] = { 38 STRING_ANSI, 39 STRING_UNICODE, 40 STRING_UNICODE_BE, 41 STRING_UTF8, 42 STRING_UTF8_BOM 43 }; 44 45 VOID ShowLastError(VOID) 46 { 47 DWORD error = GetLastError(); 48 if (error != NO_ERROR) 49 { 50 LPTSTR lpMsgBuf = NULL; 51 TCHAR szTitle[MAX_STRING_LEN]; 52 53 LoadString(Globals.hInstance, STRING_ERROR, szTitle, _countof(szTitle)); 54 55 FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, 56 NULL, 57 error, 58 0, 59 (LPTSTR) &lpMsgBuf, 60 0, 61 NULL); 62 63 MessageBox(Globals.hMainWnd, lpMsgBuf, szTitle, MB_OK | MB_ICONERROR); 64 LocalFree(lpMsgBuf); 65 } 66 } 67 68 /** 69 * Sets the caption of the main window according to Globals.szFileTitle: 70 * (untitled) - Notepad if no file is open 71 * [filename] - Notepad if a file is given 72 */ 73 void UpdateWindowCaption(BOOL clearModifyAlert) 74 { 75 TCHAR szCaption[MAX_STRING_LEN]; 76 TCHAR szNotepad[MAX_STRING_LEN]; 77 TCHAR szFilename[MAX_STRING_LEN]; 78 BOOL isModified; 79 80 if (clearModifyAlert) 81 { 82 /* When a file is being opened or created, there is no need to have 83 * the edited flag shown when the file has not been edited yet. */ 84 isModified = FALSE; 85 } 86 else 87 { 88 /* Check whether the user has modified the file or not. If we are 89 * in the same state as before, don't change the caption. */ 90 isModified = !!SendMessage(Globals.hEdit, EM_GETMODIFY, 0, 0); 91 if (isModified == Globals.bWasModified) 92 return; 93 } 94 95 /* Remember the state for later calls */ 96 Globals.bWasModified = isModified; 97 98 /* Load the name of the application */ 99 LoadString(Globals.hInstance, STRING_NOTEPAD, szNotepad, _countof(szNotepad)); 100 101 /* Determine if the file has been saved or if this is a new file */ 102 if (Globals.szFileTitle[0] != 0) 103 StringCchCopy(szFilename, _countof(szFilename), Globals.szFileTitle); 104 else 105 LoadString(Globals.hInstance, STRING_UNTITLED, szFilename, _countof(szFilename)); 106 107 /* Update the window caption based upon whether the user has modified the file or not */ 108 StringCbPrintf(szCaption, sizeof(szCaption), _T("%s%s - %s"), 109 (isModified ? _T("*") : _T("")), szFilename, szNotepad); 110 111 SetWindowText(Globals.hMainWnd, szCaption); 112 } 113 114 VOID WaitCursor(BOOL bBegin) 115 { 116 static HCURSOR s_hWaitCursor = NULL; 117 static HCURSOR s_hOldCursor = NULL; 118 static INT s_nLock = 0; 119 120 if (bBegin) 121 { 122 if (s_nLock++ == 0) 123 { 124 if (s_hWaitCursor == NULL) 125 s_hWaitCursor = LoadCursor(NULL, IDC_WAIT); 126 s_hOldCursor = SetCursor(s_hWaitCursor); 127 } 128 else 129 { 130 SetCursor(s_hWaitCursor); 131 } 132 } 133 else 134 { 135 if (--s_nLock == 0) 136 SetCursor(s_hOldCursor); 137 } 138 } 139 140 141 VOID DIALOG_StatusBarAlignParts(VOID) 142 { 143 static const int defaultWidths[] = {120, 120, 120}; 144 RECT rcStatusBar; 145 int parts[3]; 146 147 GetClientRect(Globals.hStatusBar, &rcStatusBar); 148 149 parts[0] = rcStatusBar.right - (defaultWidths[1] + defaultWidths[2]); 150 parts[1] = rcStatusBar.right - defaultWidths[2]; 151 parts[2] = -1; // the right edge of the status bar 152 153 parts[0] = max(parts[0], defaultWidths[0]); 154 parts[1] = max(parts[1], defaultWidths[0] + defaultWidths[1]); 155 156 SendMessageW(Globals.hStatusBar, SB_SETPARTS, _countof(parts), (LPARAM)parts); 157 } 158 159 static VOID DIALOG_StatusBarUpdateLineEndings(VOID) 160 { 161 WCHAR szText[128]; 162 163 LoadStringW(Globals.hInstance, EolnToStrId[Globals.iEoln], szText, _countof(szText)); 164 165 SendMessageW(Globals.hStatusBar, SB_SETTEXTW, SBPART_EOLN, (LPARAM)szText); 166 } 167 168 static VOID DIALOG_StatusBarUpdateEncoding(VOID) 169 { 170 WCHAR szText[128] = L""; 171 172 if (Globals.encFile != ENCODING_AUTO) 173 { 174 LoadStringW(Globals.hInstance, EncToStrId[Globals.encFile], szText, _countof(szText)); 175 } 176 177 SendMessageW(Globals.hStatusBar, SB_SETTEXTW, SBPART_ENCODING, (LPARAM)szText); 178 } 179 180 static VOID DIALOG_StatusBarUpdateAll(VOID) 181 { 182 DIALOG_StatusBarUpdateCaretPos(); 183 DIALOG_StatusBarUpdateLineEndings(); 184 DIALOG_StatusBarUpdateEncoding(); 185 } 186 187 int DIALOG_StringMsgBox(HWND hParent, int formatId, LPCTSTR szString, DWORD dwFlags) 188 { 189 TCHAR szMessage[MAX_STRING_LEN]; 190 TCHAR szResource[MAX_STRING_LEN]; 191 192 /* Load and format szMessage */ 193 LoadString(Globals.hInstance, formatId, szResource, _countof(szResource)); 194 StringCchPrintf(szMessage, _countof(szMessage), szResource, szString); 195 196 /* Load szCaption */ 197 if ((dwFlags & MB_ICONMASK) == MB_ICONEXCLAMATION) 198 LoadString(Globals.hInstance, STRING_ERROR, szResource, _countof(szResource)); 199 else 200 LoadString(Globals.hInstance, STRING_NOTEPAD, szResource, _countof(szResource)); 201 202 /* Display Modal Dialog */ 203 // if (hParent == NULL) 204 // hParent = Globals.hMainWnd; 205 return MessageBox(hParent, szMessage, szResource, dwFlags); 206 } 207 208 static void AlertFileNotFound(LPCTSTR szFileName) 209 { 210 DIALOG_StringMsgBox(Globals.hMainWnd, STRING_NOTFOUND, szFileName, MB_ICONEXCLAMATION | MB_OK); 211 } 212 213 static int AlertFileNotSaved(LPCTSTR szFileName) 214 { 215 TCHAR szUntitled[MAX_STRING_LEN]; 216 217 LoadString(Globals.hInstance, STRING_UNTITLED, szUntitled, _countof(szUntitled)); 218 219 return DIALOG_StringMsgBox(Globals.hMainWnd, STRING_NOTSAVED, 220 szFileName[0] ? szFileName : szUntitled, 221 MB_ICONQUESTION | MB_YESNOCANCEL); 222 } 223 224 /** 225 * Returns: 226 * TRUE - if file exists 227 * FALSE - if file does not exist 228 */ 229 BOOL FileExists(LPCTSTR szFilename) 230 { 231 return GetFileAttributes(szFilename) != INVALID_FILE_ATTRIBUTES; 232 } 233 234 BOOL HasFileExtension(LPCTSTR szFilename) 235 { 236 LPCTSTR s; 237 238 s = _tcsrchr(szFilename, _T('\\')); 239 if (s) 240 szFilename = s; 241 return _tcsrchr(szFilename, _T('.')) != NULL; 242 } 243 244 static BOOL DoSaveFile(VOID) 245 { 246 BOOL bRet = FALSE; 247 HANDLE hFile; 248 DWORD cchText; 249 250 WaitCursor(TRUE); 251 252 hFile = CreateFileW(Globals.szFileName, GENERIC_WRITE, FILE_SHARE_WRITE, 253 NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); 254 if (hFile == INVALID_HANDLE_VALUE) 255 { 256 ShowLastError(); 257 WaitCursor(FALSE); 258 return FALSE; 259 } 260 261 cchText = GetWindowTextLengthW(Globals.hEdit); 262 if (cchText <= 0) 263 { 264 bRet = TRUE; 265 } 266 else 267 { 268 HLOCAL hLocal = (HLOCAL)SendMessageW(Globals.hEdit, EM_GETHANDLE, 0, 0); 269 LPWSTR pszText = LocalLock(hLocal); 270 if (pszText) 271 { 272 bRet = WriteText(hFile, pszText, cchText, Globals.encFile, Globals.iEoln); 273 if (!bRet) 274 ShowLastError(); 275 276 LocalUnlock(hLocal); 277 } 278 else 279 { 280 ShowLastError(); 281 } 282 } 283 284 CloseHandle(hFile); 285 286 if (bRet) 287 { 288 SendMessage(Globals.hEdit, EM_SETMODIFY, FALSE, 0); 289 SetFileName(Globals.szFileName); 290 } 291 292 WaitCursor(FALSE); 293 return bRet; 294 } 295 296 /** 297 * Returns: 298 * TRUE - User agreed to close (both save/don't save) 299 * FALSE - User cancelled close by selecting "Cancel" 300 */ 301 BOOL DoCloseFile(VOID) 302 { 303 int nResult; 304 305 if (SendMessage(Globals.hEdit, EM_GETMODIFY, 0, 0)) 306 { 307 /* prompt user to save changes */ 308 nResult = AlertFileNotSaved(Globals.szFileName); 309 switch (nResult) 310 { 311 case IDYES: 312 if(!DIALOG_FileSave()) 313 return FALSE; 314 break; 315 316 case IDNO: 317 break; 318 319 case IDCANCEL: 320 default: 321 return FALSE; 322 } 323 } 324 325 SetFileName(empty_str); 326 UpdateWindowCaption(TRUE); 327 328 return TRUE; 329 } 330 331 VOID DoOpenFile(LPCTSTR szFileName) 332 { 333 HANDLE hFile; 334 TCHAR log[5]; 335 HLOCAL hLocal; 336 337 /* Close any files and prompt to save changes */ 338 if (!DoCloseFile()) 339 return; 340 341 WaitCursor(TRUE); 342 343 hFile = CreateFile(szFileName, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, 344 OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); 345 if (hFile == INVALID_HANDLE_VALUE) 346 { 347 ShowLastError(); 348 goto done; 349 } 350 351 /* To make loading file quicker, we use the internal handle of EDIT control */ 352 hLocal = (HLOCAL)SendMessageW(Globals.hEdit, EM_GETHANDLE, 0, 0); 353 if (!ReadText(hFile, &hLocal, &Globals.encFile, &Globals.iEoln)) 354 { 355 ShowLastError(); 356 goto done; 357 } 358 SendMessageW(Globals.hEdit, EM_SETHANDLE, (WPARAM)hLocal, 0); 359 /* No need of EM_SETMODIFY and EM_EMPTYUNDOBUFFER here. EM_SETHANDLE does instead. */ 360 361 SetFocus(Globals.hEdit); 362 363 /* If the file starts with .LOG, add a time/date at the end and set cursor after 364 * See http://web.archive.org/web/20090627165105/http://support.microsoft.com/kb/260563 365 */ 366 if (GetWindowText(Globals.hEdit, log, _countof(log)) && !_tcscmp(log, _T(".LOG"))) 367 { 368 static const TCHAR lf[] = _T("\r\n"); 369 SendMessage(Globals.hEdit, EM_SETSEL, GetWindowTextLength(Globals.hEdit), -1); 370 SendMessage(Globals.hEdit, EM_REPLACESEL, TRUE, (LPARAM)lf); 371 DIALOG_EditTimeDate(); 372 SendMessage(Globals.hEdit, EM_REPLACESEL, TRUE, (LPARAM)lf); 373 } 374 375 SetFileName(szFileName); 376 UpdateWindowCaption(TRUE); 377 NOTEPAD_EnableSearchMenu(); 378 DIALOG_StatusBarUpdateAll(); 379 380 done: 381 if (hFile != INVALID_HANDLE_VALUE) 382 CloseHandle(hFile); 383 WaitCursor(FALSE); 384 } 385 386 VOID DIALOG_FileNew(VOID) 387 { 388 /* Close any files and prompt to save changes */ 389 if (!DoCloseFile()) 390 return; 391 392 WaitCursor(TRUE); 393 394 SetWindowText(Globals.hEdit, NULL); 395 SendMessage(Globals.hEdit, EM_EMPTYUNDOBUFFER, 0, 0); 396 Globals.iEoln = EOLN_CRLF; 397 Globals.encFile = ENCODING_DEFAULT; 398 399 NOTEPAD_EnableSearchMenu(); 400 DIALOG_StatusBarUpdateAll(); 401 402 WaitCursor(FALSE); 403 } 404 405 VOID DIALOG_FileNewWindow(VOID) 406 { 407 TCHAR pszNotepadExe[MAX_PATH]; 408 409 WaitCursor(TRUE); 410 411 GetModuleFileName(NULL, pszNotepadExe, _countof(pszNotepadExe)); 412 ShellExecute(NULL, NULL, pszNotepadExe, NULL, NULL, SW_SHOWNORMAL); 413 414 WaitCursor(FALSE); 415 } 416 417 VOID DIALOG_FileOpen(VOID) 418 { 419 OPENFILENAME openfilename; 420 TCHAR szPath[MAX_PATH]; 421 422 ZeroMemory(&openfilename, sizeof(openfilename)); 423 424 if (Globals.szFileName[0] == 0) 425 _tcscpy(szPath, txt_files); 426 else 427 _tcscpy(szPath, Globals.szFileName); 428 429 openfilename.lStructSize = sizeof(openfilename); 430 openfilename.hwndOwner = Globals.hMainWnd; 431 openfilename.hInstance = Globals.hInstance; 432 openfilename.lpstrFilter = Globals.szFilter; 433 openfilename.lpstrFile = szPath; 434 openfilename.nMaxFile = _countof(szPath); 435 openfilename.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY; 436 openfilename.lpstrDefExt = szDefaultExt; 437 438 if (GetOpenFileName(&openfilename)) { 439 if (FileExists(openfilename.lpstrFile)) 440 DoOpenFile(openfilename.lpstrFile); 441 else 442 AlertFileNotFound(openfilename.lpstrFile); 443 } 444 } 445 446 BOOL DIALOG_FileSave(VOID) 447 { 448 if (Globals.szFileName[0] == 0) 449 { 450 return DIALOG_FileSaveAs(); 451 } 452 else if (DoSaveFile()) 453 { 454 UpdateWindowCaption(TRUE); 455 return TRUE; 456 } 457 return FALSE; 458 } 459 460 static UINT_PTR 461 CALLBACK 462 DIALOG_FileSaveAs_Hook(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam) 463 { 464 TCHAR szText[128]; 465 HWND hCombo; 466 467 UNREFERENCED_PARAMETER(wParam); 468 469 switch(msg) 470 { 471 case WM_INITDIALOG: 472 hCombo = GetDlgItem(hDlg, ID_ENCODING); 473 474 LoadString(Globals.hInstance, STRING_ANSI, szText, _countof(szText)); 475 SendMessage(hCombo, CB_ADDSTRING, 0, (LPARAM) szText); 476 477 LoadString(Globals.hInstance, STRING_UNICODE, szText, _countof(szText)); 478 SendMessage(hCombo, CB_ADDSTRING, 0, (LPARAM) szText); 479 480 LoadString(Globals.hInstance, STRING_UNICODE_BE, szText, _countof(szText)); 481 SendMessage(hCombo, CB_ADDSTRING, 0, (LPARAM) szText); 482 483 LoadString(Globals.hInstance, STRING_UTF8, szText, _countof(szText)); 484 SendMessage(hCombo, CB_ADDSTRING, 0, (LPARAM) szText); 485 486 LoadString(Globals.hInstance, STRING_UTF8_BOM, szText, _countof(szText)); 487 SendMessage(hCombo, CB_ADDSTRING, 0, (LPARAM) szText); 488 489 SendMessage(hCombo, CB_SETCURSEL, Globals.encFile, 0); 490 491 hCombo = GetDlgItem(hDlg, ID_EOLN); 492 493 LoadString(Globals.hInstance, STRING_CRLF, szText, _countof(szText)); 494 SendMessage(hCombo, CB_ADDSTRING, 0, (LPARAM) szText); 495 496 LoadString(Globals.hInstance, STRING_LF, szText, _countof(szText)); 497 SendMessage(hCombo, CB_ADDSTRING, 0, (LPARAM) szText); 498 499 LoadString(Globals.hInstance, STRING_CR, szText, _countof(szText)); 500 SendMessage(hCombo, CB_ADDSTRING, 0, (LPARAM) szText); 501 502 SendMessage(hCombo, CB_SETCURSEL, Globals.iEoln, 0); 503 break; 504 505 case WM_NOTIFY: 506 if (((NMHDR *) lParam)->code == CDN_FILEOK) 507 { 508 hCombo = GetDlgItem(hDlg, ID_ENCODING); 509 if (hCombo) 510 Globals.encFile = (ENCODING) SendMessage(hCombo, CB_GETCURSEL, 0, 0); 511 512 hCombo = GetDlgItem(hDlg, ID_EOLN); 513 if (hCombo) 514 Globals.iEoln = (EOLN)SendMessage(hCombo, CB_GETCURSEL, 0, 0); 515 } 516 break; 517 } 518 return 0; 519 } 520 521 BOOL DIALOG_FileSaveAs(VOID) 522 { 523 OPENFILENAME saveas; 524 TCHAR szPath[MAX_PATH]; 525 526 ZeroMemory(&saveas, sizeof(saveas)); 527 528 if (Globals.szFileName[0] == 0) 529 _tcscpy(szPath, txt_files); 530 else 531 _tcscpy(szPath, Globals.szFileName); 532 533 saveas.lStructSize = sizeof(OPENFILENAME); 534 saveas.hwndOwner = Globals.hMainWnd; 535 saveas.hInstance = Globals.hInstance; 536 saveas.lpstrFilter = Globals.szFilter; 537 saveas.lpstrFile = szPath; 538 saveas.nMaxFile = _countof(szPath); 539 saveas.Flags = OFN_PATHMUSTEXIST | OFN_OVERWRITEPROMPT | OFN_HIDEREADONLY | 540 OFN_EXPLORER | OFN_ENABLETEMPLATE | OFN_ENABLEHOOK; 541 saveas.lpstrDefExt = szDefaultExt; 542 saveas.lpTemplateName = MAKEINTRESOURCE(DIALOG_ENCODING); 543 saveas.lpfnHook = DIALOG_FileSaveAs_Hook; 544 545 if (GetSaveFileName(&saveas)) 546 { 547 /* HACK: Because in ROS, Save-As boxes don't check the validity 548 * of file names and thus, here, szPath can be invalid !! We only 549 * see its validity when we call DoSaveFile()... */ 550 SetFileName(szPath); 551 if (DoSaveFile()) 552 { 553 UpdateWindowCaption(TRUE); 554 DIALOG_StatusBarUpdateAll(); 555 return TRUE; 556 } 557 else 558 { 559 SetFileName(_T("")); 560 return FALSE; 561 } 562 } 563 else 564 { 565 return FALSE; 566 } 567 } 568 569 VOID DIALOG_FileExit(VOID) 570 { 571 PostMessage(Globals.hMainWnd, WM_CLOSE, 0, 0); 572 } 573 574 VOID DIALOG_EditUndo(VOID) 575 { 576 SendMessage(Globals.hEdit, EM_UNDO, 0, 0); 577 } 578 579 VOID DIALOG_EditCut(VOID) 580 { 581 SendMessage(Globals.hEdit, WM_CUT, 0, 0); 582 } 583 584 VOID DIALOG_EditCopy(VOID) 585 { 586 SendMessage(Globals.hEdit, WM_COPY, 0, 0); 587 } 588 589 VOID DIALOG_EditPaste(VOID) 590 { 591 SendMessage(Globals.hEdit, WM_PASTE, 0, 0); 592 } 593 594 VOID DIALOG_EditDelete(VOID) 595 { 596 SendMessage(Globals.hEdit, WM_CLEAR, 0, 0); 597 } 598 599 VOID DIALOG_EditSelectAll(VOID) 600 { 601 SendMessage(Globals.hEdit, EM_SETSEL, 0, -1); 602 } 603 604 VOID DIALOG_EditTimeDate(VOID) 605 { 606 SYSTEMTIME st; 607 TCHAR szDate[MAX_STRING_LEN]; 608 TCHAR szText[MAX_STRING_LEN * 2 + 2]; 609 610 GetLocalTime(&st); 611 612 GetTimeFormat(LOCALE_USER_DEFAULT, 0, &st, NULL, szDate, MAX_STRING_LEN); 613 _tcscpy(szText, szDate); 614 _tcscat(szText, _T(" ")); 615 GetDateFormat(LOCALE_USER_DEFAULT, DATE_LONGDATE, &st, NULL, szDate, MAX_STRING_LEN); 616 _tcscat(szText, szDate); 617 SendMessage(Globals.hEdit, EM_REPLACESEL, TRUE, (LPARAM)szText); 618 } 619 620 VOID DoShowHideStatusBar(VOID) 621 { 622 /* Check if status bar object already exists. */ 623 if (Globals.bShowStatusBar && Globals.hStatusBar == NULL) 624 { 625 /* Try to create the status bar */ 626 Globals.hStatusBar = CreateStatusWindow(WS_CHILD | CCS_BOTTOM | SBARS_SIZEGRIP, 627 NULL, 628 Globals.hMainWnd, 629 CMD_STATUSBAR_WND_ID); 630 631 if (Globals.hStatusBar == NULL) 632 { 633 ShowLastError(); 634 return; 635 } 636 637 /* Load the string for formatting column/row text output */ 638 LoadString(Globals.hInstance, STRING_LINE_COLUMN, Globals.szStatusBarLineCol, MAX_PATH - 1); 639 } 640 641 /* Update layout of controls */ 642 SendMessageW(Globals.hMainWnd, WM_SIZE, 0, 0); 643 644 if (Globals.hStatusBar == NULL) 645 return; 646 647 /* Update visibility of status bar */ 648 ShowWindow(Globals.hStatusBar, (Globals.bShowStatusBar ? SW_SHOWNOACTIVATE : SW_HIDE)); 649 650 /* Update status bar contents */ 651 DIALOG_StatusBarUpdateAll(); 652 } 653 654 VOID DoCreateEditWindow(VOID) 655 { 656 DWORD dwStyle; 657 int iSize; 658 LPTSTR pTemp = NULL; 659 BOOL bModified = FALSE; 660 661 iSize = 0; 662 663 /* If the edit control already exists, try to save its content */ 664 if (Globals.hEdit != NULL) 665 { 666 /* number of chars currently written into the editor. */ 667 iSize = GetWindowTextLength(Globals.hEdit); 668 if (iSize) 669 { 670 /* Allocates temporary buffer. */ 671 pTemp = HeapAlloc(GetProcessHeap(), 0, (iSize + 1) * sizeof(TCHAR)); 672 if (!pTemp) 673 { 674 ShowLastError(); 675 return; 676 } 677 678 /* Recover the text into the control. */ 679 GetWindowText(Globals.hEdit, pTemp, iSize + 1); 680 681 if (SendMessage(Globals.hEdit, EM_GETMODIFY, 0, 0)) 682 bModified = TRUE; 683 } 684 685 /* Restore original window procedure */ 686 SetWindowLongPtr(Globals.hEdit, GWLP_WNDPROC, (LONG_PTR)Globals.EditProc); 687 688 /* Destroy the edit control */ 689 DestroyWindow(Globals.hEdit); 690 } 691 692 /* Update wrap status into the main menu and recover style flags */ 693 dwStyle = (Globals.bWrapLongLines ? EDIT_STYLE_WRAP : EDIT_STYLE); 694 695 /* Create the new edit control */ 696 Globals.hEdit = CreateWindowEx(WS_EX_CLIENTEDGE, 697 EDIT_CLASS, 698 NULL, 699 dwStyle, 700 CW_USEDEFAULT, 701 CW_USEDEFAULT, 702 CW_USEDEFAULT, 703 CW_USEDEFAULT, 704 Globals.hMainWnd, 705 NULL, 706 Globals.hInstance, 707 NULL); 708 if (Globals.hEdit == NULL) 709 { 710 if (pTemp) 711 { 712 HeapFree(GetProcessHeap(), 0, pTemp); 713 } 714 715 ShowLastError(); 716 return; 717 } 718 719 SendMessage(Globals.hEdit, WM_SETFONT, (WPARAM)Globals.hFont, FALSE); 720 SendMessage(Globals.hEdit, EM_LIMITTEXT, 0, 0); 721 722 /* If some text was previously saved, restore it. */ 723 if (iSize != 0) 724 { 725 SetWindowText(Globals.hEdit, pTemp); 726 HeapFree(GetProcessHeap(), 0, pTemp); 727 728 if (bModified) 729 SendMessage(Globals.hEdit, EM_SETMODIFY, TRUE, 0); 730 } 731 732 /* Sub-class a new window callback for row/column detection. */ 733 Globals.EditProc = (WNDPROC)SetWindowLongPtr(Globals.hEdit, 734 GWLP_WNDPROC, 735 (LONG_PTR)EDIT_WndProc); 736 737 /* Finally shows new edit control and set focus into it. */ 738 ShowWindow(Globals.hEdit, SW_SHOW); 739 SetFocus(Globals.hEdit); 740 741 /* Re-arrange controls */ 742 PostMessageW(Globals.hMainWnd, WM_SIZE, 0, 0); 743 } 744 745 VOID DIALOG_EditWrap(VOID) 746 { 747 Globals.bWrapLongLines = !Globals.bWrapLongLines; 748 749 EnableMenuItem(Globals.hMenu, CMD_GOTO, (Globals.bWrapLongLines ? MF_GRAYED : MF_ENABLED)); 750 751 DoCreateEditWindow(); 752 DoShowHideStatusBar(); 753 } 754 755 VOID DIALOG_SelectFont(VOID) 756 { 757 CHOOSEFONT cf; 758 LOGFONT lf = Globals.lfFont; 759 760 ZeroMemory( &cf, sizeof(cf) ); 761 cf.lStructSize = sizeof(cf); 762 cf.hwndOwner = Globals.hMainWnd; 763 cf.lpLogFont = &lf; 764 cf.Flags = CF_SCREENFONTS | CF_INITTOLOGFONTSTRUCT | CF_NOVERTFONTS; 765 766 if (ChooseFont(&cf)) 767 { 768 HFONT currfont = Globals.hFont; 769 770 Globals.hFont = CreateFontIndirect(&lf); 771 Globals.lfFont = lf; 772 SendMessage(Globals.hEdit, WM_SETFONT, (WPARAM)Globals.hFont, TRUE); 773 if (currfont != NULL) 774 DeleteObject(currfont); 775 } 776 } 777 778 typedef HWND (WINAPI *FINDPROC)(LPFINDREPLACE lpfr); 779 780 static VOID DIALOG_SearchDialog(FINDPROC pfnProc) 781 { 782 if (Globals.hFindReplaceDlg != NULL) 783 { 784 SetFocus(Globals.hFindReplaceDlg); 785 return; 786 } 787 788 if (!Globals.find.lpstrFindWhat) 789 { 790 ZeroMemory(&Globals.find, sizeof(Globals.find)); 791 Globals.find.lStructSize = sizeof(Globals.find); 792 Globals.find.hwndOwner = Globals.hMainWnd; 793 Globals.find.lpstrFindWhat = Globals.szFindText; 794 Globals.find.wFindWhatLen = _countof(Globals.szFindText); 795 Globals.find.lpstrReplaceWith = Globals.szReplaceText; 796 Globals.find.wReplaceWithLen = _countof(Globals.szReplaceText); 797 Globals.find.Flags = FR_DOWN; 798 } 799 800 /* We only need to create the modal FindReplace dialog which will */ 801 /* notify us of incoming events using hMainWnd Window Messages */ 802 803 Globals.hFindReplaceDlg = pfnProc(&Globals.find); 804 assert(Globals.hFindReplaceDlg != NULL); 805 } 806 807 VOID DIALOG_Search(VOID) 808 { 809 DIALOG_SearchDialog(FindText); 810 } 811 812 VOID DIALOG_SearchNext(BOOL bDown) 813 { 814 if (bDown) 815 Globals.find.Flags |= FR_DOWN; 816 else 817 Globals.find.Flags &= ~FR_DOWN; 818 819 if (Globals.find.lpstrFindWhat != NULL) 820 NOTEPAD_FindNext(&Globals.find, FALSE, TRUE); 821 else 822 DIALOG_Search(); 823 } 824 825 VOID DIALOG_Replace(VOID) 826 { 827 DIALOG_SearchDialog(ReplaceText); 828 } 829 830 typedef struct tagGOTO_DATA 831 { 832 UINT iLine; 833 UINT cLines; 834 } GOTO_DATA, *PGOTO_DATA; 835 836 static INT_PTR 837 CALLBACK 838 DIALOG_GoTo_DialogProc(HWND hwndDialog, UINT uMsg, WPARAM wParam, LPARAM lParam) 839 { 840 static PGOTO_DATA s_pGotoData; 841 842 switch (uMsg) 843 { 844 case WM_INITDIALOG: 845 s_pGotoData = (PGOTO_DATA)lParam; 846 SetDlgItemInt(hwndDialog, ID_LINENUMBER, s_pGotoData->iLine, FALSE); 847 return TRUE; /* Set focus */ 848 849 case WM_COMMAND: 850 { 851 if (LOWORD(wParam) == IDOK) 852 { 853 UINT iLine = GetDlgItemInt(hwndDialog, ID_LINENUMBER, NULL, FALSE); 854 if (iLine <= 0 || s_pGotoData->cLines < iLine) /* Out of range */ 855 { 856 /* Show error message */ 857 WCHAR title[128], text[256]; 858 LoadStringW(Globals.hInstance, STRING_NOTEPAD, title, _countof(title)); 859 LoadStringW(Globals.hInstance, STRING_LINE_NUMBER_OUT_OF_RANGE, text, _countof(text)); 860 MessageBoxW(hwndDialog, text, title, MB_OK); 861 862 SendDlgItemMessageW(hwndDialog, ID_LINENUMBER, EM_SETSEL, 0, -1); 863 SetFocus(GetDlgItem(hwndDialog, ID_LINENUMBER)); 864 break; 865 } 866 s_pGotoData->iLine = iLine; 867 EndDialog(hwndDialog, IDOK); 868 } 869 else if (LOWORD(wParam) == IDCANCEL) 870 { 871 EndDialog(hwndDialog, IDCANCEL); 872 } 873 break; 874 } 875 } 876 877 return 0; 878 } 879 880 VOID DIALOG_GoTo(VOID) 881 { 882 GOTO_DATA GotoData; 883 DWORD dwStart = 0, dwEnd = 0; 884 INT ich, cch = GetWindowTextLength(Globals.hEdit); 885 886 /* Get the current line number and the total line number */ 887 SendMessage(Globals.hEdit, EM_GETSEL, (WPARAM) &dwStart, (LPARAM) &dwEnd); 888 GotoData.iLine = (UINT)SendMessage(Globals.hEdit, EM_LINEFROMCHAR, dwStart, 0) + 1; 889 GotoData.cLines = (UINT)SendMessage(Globals.hEdit, EM_GETLINECOUNT, 0, 0); 890 891 /* Ask the user for line number */ 892 if (DialogBoxParam(Globals.hInstance, 893 MAKEINTRESOURCE(DIALOG_GOTO), 894 Globals.hMainWnd, 895 DIALOG_GoTo_DialogProc, 896 (LPARAM)&GotoData) != IDOK) 897 { 898 return; /* Canceled */ 899 } 900 901 --GotoData.iLine; /* Make it zero-based */ 902 903 /* Get ich (the target character index) from line number */ 904 if (GotoData.iLine <= 0) 905 ich = 0; 906 else if (GotoData.iLine >= GotoData.cLines) 907 ich = cch; 908 else 909 ich = (INT)SendMessage(Globals.hEdit, EM_LINEINDEX, GotoData.iLine, 0); 910 911 /* EM_LINEINDEX can return -1 on failure */ 912 if (ich < 0) 913 ich = 0; 914 915 /* Move the caret */ 916 SendMessage(Globals.hEdit, EM_SETSEL, ich, ich); 917 SendMessage(Globals.hEdit, EM_SCROLLCARET, 0, 0); 918 } 919 920 VOID DIALOG_StatusBarUpdateCaretPos(VOID) 921 { 922 int line, ich, col; 923 TCHAR buff[MAX_PATH]; 924 DWORD dwStart, dwSize; 925 926 SendMessage(Globals.hEdit, EM_GETSEL, (WPARAM)&dwStart, (LPARAM)&dwSize); 927 line = SendMessage(Globals.hEdit, EM_LINEFROMCHAR, (WPARAM)dwStart, 0); 928 ich = (int)SendMessage(Globals.hEdit, EM_LINEINDEX, (WPARAM)line, 0); 929 930 /* EM_LINEINDEX can return -1 on failure */ 931 col = ((ich < 0) ? 0 : (dwStart - ich)); 932 933 StringCchPrintf(buff, _countof(buff), Globals.szStatusBarLineCol, line + 1, col + 1); 934 SendMessage(Globals.hStatusBar, SB_SETTEXT, SBPART_CURPOS, (LPARAM)buff); 935 } 936 937 VOID DIALOG_ViewStatusBar(VOID) 938 { 939 Globals.bShowStatusBar = !Globals.bShowStatusBar; 940 DoShowHideStatusBar(); 941 } 942 943 VOID DIALOG_HelpContents(VOID) 944 { 945 WinHelp(Globals.hMainWnd, helpfile, HELP_INDEX, 0); 946 } 947 948 VOID DIALOG_HelpAboutNotepad(VOID) 949 { 950 TCHAR szNotepad[MAX_STRING_LEN]; 951 TCHAR szNotepadAuthors[MAX_STRING_LEN]; 952 953 LoadString(Globals.hInstance, STRING_NOTEPAD, szNotepad, _countof(szNotepad)); 954 LoadString(Globals.hInstance, STRING_NOTEPAD_AUTHORS, szNotepadAuthors, _countof(szNotepadAuthors)); 955 956 ShellAbout(Globals.hMainWnd, szNotepad, szNotepadAuthors, 957 LoadIcon(Globals.hInstance, MAKEINTRESOURCE(IDI_NPICON))); 958 } 959