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 /* Use OPEN_ALWAYS instead of CREATE_ALWAYS in order to succeed 253 * even if the file has HIDDEN or SYSTEM attributes */ 254 hFile = CreateFileW(Globals.szFileName, GENERIC_WRITE, FILE_SHARE_READ, 255 NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); 256 if (hFile == INVALID_HANDLE_VALUE) 257 { 258 ShowLastError(); 259 WaitCursor(FALSE); 260 return FALSE; 261 } 262 263 cchText = GetWindowTextLengthW(Globals.hEdit); 264 if (cchText <= 0) 265 { 266 bRet = TRUE; 267 } 268 else 269 { 270 HLOCAL hLocal = (HLOCAL)SendMessageW(Globals.hEdit, EM_GETHANDLE, 0, 0); 271 LPWSTR pszText = LocalLock(hLocal); 272 if (pszText) 273 { 274 bRet = WriteText(hFile, pszText, cchText, Globals.encFile, Globals.iEoln); 275 if (!bRet) 276 ShowLastError(); 277 278 LocalUnlock(hLocal); 279 } 280 else 281 { 282 ShowLastError(); 283 } 284 } 285 286 /* Truncate the file and close it */ 287 SetEndOfFile(hFile); 288 CloseHandle(hFile); 289 290 if (bRet) 291 { 292 SendMessage(Globals.hEdit, EM_SETMODIFY, FALSE, 0); 293 SetFileName(Globals.szFileName); 294 } 295 296 WaitCursor(FALSE); 297 return bRet; 298 } 299 300 /** 301 * Returns: 302 * TRUE - User agreed to close (both save/don't save) 303 * FALSE - User cancelled close by selecting "Cancel" 304 */ 305 BOOL DoCloseFile(VOID) 306 { 307 int nResult; 308 309 if (SendMessage(Globals.hEdit, EM_GETMODIFY, 0, 0)) 310 { 311 /* prompt user to save changes */ 312 nResult = AlertFileNotSaved(Globals.szFileName); 313 switch (nResult) 314 { 315 case IDYES: 316 if(!DIALOG_FileSave()) 317 return FALSE; 318 break; 319 320 case IDNO: 321 break; 322 323 case IDCANCEL: 324 default: 325 return FALSE; 326 } 327 } 328 329 SetFileName(empty_str); 330 UpdateWindowCaption(TRUE); 331 332 return TRUE; 333 } 334 335 VOID DoOpenFile(LPCTSTR szFileName) 336 { 337 HANDLE hFile; 338 TCHAR log[5]; 339 HLOCAL hLocal; 340 341 /* Close any files and prompt to save changes */ 342 if (!DoCloseFile()) 343 return; 344 345 WaitCursor(TRUE); 346 347 hFile = CreateFile(szFileName, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, 348 OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); 349 if (hFile == INVALID_HANDLE_VALUE) 350 { 351 ShowLastError(); 352 goto done; 353 } 354 355 /* To make loading file quicker, we use the internal handle of EDIT control */ 356 hLocal = (HLOCAL)SendMessageW(Globals.hEdit, EM_GETHANDLE, 0, 0); 357 if (!ReadText(hFile, &hLocal, &Globals.encFile, &Globals.iEoln)) 358 { 359 ShowLastError(); 360 goto done; 361 } 362 SendMessageW(Globals.hEdit, EM_SETHANDLE, (WPARAM)hLocal, 0); 363 /* No need of EM_SETMODIFY and EM_EMPTYUNDOBUFFER here. EM_SETHANDLE does instead. */ 364 365 SetFocus(Globals.hEdit); 366 367 /* If the file starts with .LOG, add a time/date at the end and set cursor after 368 * See http://web.archive.org/web/20090627165105/http://support.microsoft.com/kb/260563 369 */ 370 if (GetWindowText(Globals.hEdit, log, _countof(log)) && !_tcscmp(log, _T(".LOG"))) 371 { 372 static const TCHAR lf[] = _T("\r\n"); 373 SendMessage(Globals.hEdit, EM_SETSEL, GetWindowTextLength(Globals.hEdit), -1); 374 SendMessage(Globals.hEdit, EM_REPLACESEL, TRUE, (LPARAM)lf); 375 DIALOG_EditTimeDate(); 376 SendMessage(Globals.hEdit, EM_REPLACESEL, TRUE, (LPARAM)lf); 377 } 378 379 SetFileName(szFileName); 380 UpdateWindowCaption(TRUE); 381 NOTEPAD_EnableSearchMenu(); 382 DIALOG_StatusBarUpdateAll(); 383 384 done: 385 if (hFile != INVALID_HANDLE_VALUE) 386 CloseHandle(hFile); 387 WaitCursor(FALSE); 388 } 389 390 VOID DIALOG_FileNew(VOID) 391 { 392 /* Close any files and prompt to save changes */ 393 if (!DoCloseFile()) 394 return; 395 396 WaitCursor(TRUE); 397 398 SetWindowText(Globals.hEdit, NULL); 399 SendMessage(Globals.hEdit, EM_EMPTYUNDOBUFFER, 0, 0); 400 Globals.iEoln = EOLN_CRLF; 401 Globals.encFile = ENCODING_DEFAULT; 402 403 NOTEPAD_EnableSearchMenu(); 404 DIALOG_StatusBarUpdateAll(); 405 406 WaitCursor(FALSE); 407 } 408 409 VOID DIALOG_FileNewWindow(VOID) 410 { 411 TCHAR pszNotepadExe[MAX_PATH]; 412 413 WaitCursor(TRUE); 414 415 GetModuleFileName(NULL, pszNotepadExe, _countof(pszNotepadExe)); 416 ShellExecute(NULL, NULL, pszNotepadExe, NULL, NULL, SW_SHOWNORMAL); 417 418 WaitCursor(FALSE); 419 } 420 421 VOID DIALOG_FileOpen(VOID) 422 { 423 OPENFILENAME openfilename; 424 TCHAR szPath[MAX_PATH]; 425 426 ZeroMemory(&openfilename, sizeof(openfilename)); 427 428 if (Globals.szFileName[0] == 0) 429 _tcscpy(szPath, txt_files); 430 else 431 _tcscpy(szPath, Globals.szFileName); 432 433 openfilename.lStructSize = sizeof(openfilename); 434 openfilename.hwndOwner = Globals.hMainWnd; 435 openfilename.hInstance = Globals.hInstance; 436 openfilename.lpstrFilter = Globals.szFilter; 437 openfilename.lpstrFile = szPath; 438 openfilename.nMaxFile = _countof(szPath); 439 openfilename.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY; 440 openfilename.lpstrDefExt = szDefaultExt; 441 442 if (GetOpenFileName(&openfilename)) { 443 if (FileExists(openfilename.lpstrFile)) 444 DoOpenFile(openfilename.lpstrFile); 445 else 446 AlertFileNotFound(openfilename.lpstrFile); 447 } 448 } 449 450 BOOL DIALOG_FileSave(VOID) 451 { 452 if (Globals.szFileName[0] == 0) 453 { 454 return DIALOG_FileSaveAs(); 455 } 456 else if (DoSaveFile()) 457 { 458 UpdateWindowCaption(TRUE); 459 return TRUE; 460 } 461 return FALSE; 462 } 463 464 static UINT_PTR 465 CALLBACK 466 DIALOG_FileSaveAs_Hook(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam) 467 { 468 TCHAR szText[128]; 469 HWND hCombo; 470 471 UNREFERENCED_PARAMETER(wParam); 472 473 switch(msg) 474 { 475 case WM_INITDIALOG: 476 hCombo = GetDlgItem(hDlg, ID_ENCODING); 477 478 LoadString(Globals.hInstance, STRING_ANSI, szText, _countof(szText)); 479 SendMessage(hCombo, CB_ADDSTRING, 0, (LPARAM) szText); 480 481 LoadString(Globals.hInstance, STRING_UNICODE, szText, _countof(szText)); 482 SendMessage(hCombo, CB_ADDSTRING, 0, (LPARAM) szText); 483 484 LoadString(Globals.hInstance, STRING_UNICODE_BE, szText, _countof(szText)); 485 SendMessage(hCombo, CB_ADDSTRING, 0, (LPARAM) szText); 486 487 LoadString(Globals.hInstance, STRING_UTF8, szText, _countof(szText)); 488 SendMessage(hCombo, CB_ADDSTRING, 0, (LPARAM) szText); 489 490 LoadString(Globals.hInstance, STRING_UTF8_BOM, szText, _countof(szText)); 491 SendMessage(hCombo, CB_ADDSTRING, 0, (LPARAM) szText); 492 493 SendMessage(hCombo, CB_SETCURSEL, Globals.encFile, 0); 494 495 hCombo = GetDlgItem(hDlg, ID_EOLN); 496 497 LoadString(Globals.hInstance, STRING_CRLF, szText, _countof(szText)); 498 SendMessage(hCombo, CB_ADDSTRING, 0, (LPARAM) szText); 499 500 LoadString(Globals.hInstance, STRING_LF, szText, _countof(szText)); 501 SendMessage(hCombo, CB_ADDSTRING, 0, (LPARAM) szText); 502 503 LoadString(Globals.hInstance, STRING_CR, szText, _countof(szText)); 504 SendMessage(hCombo, CB_ADDSTRING, 0, (LPARAM) szText); 505 506 SendMessage(hCombo, CB_SETCURSEL, Globals.iEoln, 0); 507 break; 508 509 case WM_NOTIFY: 510 if (((NMHDR *) lParam)->code == CDN_FILEOK) 511 { 512 hCombo = GetDlgItem(hDlg, ID_ENCODING); 513 if (hCombo) 514 Globals.encFile = (ENCODING) SendMessage(hCombo, CB_GETCURSEL, 0, 0); 515 516 hCombo = GetDlgItem(hDlg, ID_EOLN); 517 if (hCombo) 518 Globals.iEoln = (EOLN)SendMessage(hCombo, CB_GETCURSEL, 0, 0); 519 } 520 break; 521 } 522 return 0; 523 } 524 525 BOOL DIALOG_FileSaveAs(VOID) 526 { 527 OPENFILENAME saveas; 528 TCHAR szPath[MAX_PATH]; 529 530 ZeroMemory(&saveas, sizeof(saveas)); 531 532 if (Globals.szFileName[0] == 0) 533 _tcscpy(szPath, txt_files); 534 else 535 _tcscpy(szPath, Globals.szFileName); 536 537 saveas.lStructSize = sizeof(OPENFILENAME); 538 saveas.hwndOwner = Globals.hMainWnd; 539 saveas.hInstance = Globals.hInstance; 540 saveas.lpstrFilter = Globals.szFilter; 541 saveas.lpstrFile = szPath; 542 saveas.nMaxFile = _countof(szPath); 543 saveas.Flags = OFN_PATHMUSTEXIST | OFN_OVERWRITEPROMPT | OFN_HIDEREADONLY | 544 OFN_EXPLORER | OFN_ENABLETEMPLATE | OFN_ENABLEHOOK; 545 saveas.lpstrDefExt = szDefaultExt; 546 saveas.lpTemplateName = MAKEINTRESOURCE(DIALOG_ENCODING); 547 saveas.lpfnHook = DIALOG_FileSaveAs_Hook; 548 549 if (GetSaveFileName(&saveas)) 550 { 551 /* HACK: Because in ROS, Save-As boxes don't check the validity 552 * of file names and thus, here, szPath can be invalid !! We only 553 * see its validity when we call DoSaveFile()... */ 554 SetFileName(szPath); 555 if (DoSaveFile()) 556 { 557 UpdateWindowCaption(TRUE); 558 DIALOG_StatusBarUpdateAll(); 559 return TRUE; 560 } 561 else 562 { 563 SetFileName(_T("")); 564 return FALSE; 565 } 566 } 567 else 568 { 569 return FALSE; 570 } 571 } 572 573 VOID DIALOG_FileExit(VOID) 574 { 575 PostMessage(Globals.hMainWnd, WM_CLOSE, 0, 0); 576 } 577 578 VOID DIALOG_EditUndo(VOID) 579 { 580 SendMessage(Globals.hEdit, EM_UNDO, 0, 0); 581 } 582 583 VOID DIALOG_EditCut(VOID) 584 { 585 SendMessage(Globals.hEdit, WM_CUT, 0, 0); 586 } 587 588 VOID DIALOG_EditCopy(VOID) 589 { 590 SendMessage(Globals.hEdit, WM_COPY, 0, 0); 591 } 592 593 VOID DIALOG_EditPaste(VOID) 594 { 595 SendMessage(Globals.hEdit, WM_PASTE, 0, 0); 596 } 597 598 VOID DIALOG_EditDelete(VOID) 599 { 600 SendMessage(Globals.hEdit, WM_CLEAR, 0, 0); 601 } 602 603 VOID DIALOG_EditSelectAll(VOID) 604 { 605 SendMessage(Globals.hEdit, EM_SETSEL, 0, -1); 606 } 607 608 VOID DIALOG_EditTimeDate(VOID) 609 { 610 SYSTEMTIME st; 611 TCHAR szDate[MAX_STRING_LEN]; 612 TCHAR szText[MAX_STRING_LEN * 2 + 2]; 613 614 GetLocalTime(&st); 615 616 GetTimeFormat(LOCALE_USER_DEFAULT, 0, &st, NULL, szDate, MAX_STRING_LEN); 617 _tcscpy(szText, szDate); 618 _tcscat(szText, _T(" ")); 619 GetDateFormat(LOCALE_USER_DEFAULT, DATE_LONGDATE, &st, NULL, szDate, MAX_STRING_LEN); 620 _tcscat(szText, szDate); 621 SendMessage(Globals.hEdit, EM_REPLACESEL, TRUE, (LPARAM)szText); 622 } 623 624 VOID DoShowHideStatusBar(VOID) 625 { 626 /* Check if status bar object already exists. */ 627 if (Globals.bShowStatusBar && Globals.hStatusBar == NULL) 628 { 629 /* Try to create the status bar */ 630 Globals.hStatusBar = CreateStatusWindow(WS_CHILD | CCS_BOTTOM | SBARS_SIZEGRIP, 631 NULL, 632 Globals.hMainWnd, 633 CMD_STATUSBAR_WND_ID); 634 635 if (Globals.hStatusBar == NULL) 636 { 637 ShowLastError(); 638 return; 639 } 640 641 /* Load the string for formatting column/row text output */ 642 LoadString(Globals.hInstance, STRING_LINE_COLUMN, Globals.szStatusBarLineCol, MAX_PATH - 1); 643 } 644 645 /* Update layout of controls */ 646 SendMessageW(Globals.hMainWnd, WM_SIZE, 0, 0); 647 648 if (Globals.hStatusBar == NULL) 649 return; 650 651 /* Update visibility of status bar */ 652 ShowWindow(Globals.hStatusBar, (Globals.bShowStatusBar ? SW_SHOWNOACTIVATE : SW_HIDE)); 653 654 /* Update status bar contents */ 655 DIALOG_StatusBarUpdateAll(); 656 } 657 658 VOID DoCreateEditWindow(VOID) 659 { 660 DWORD dwStyle; 661 int iSize; 662 LPTSTR pTemp = NULL; 663 BOOL bModified = FALSE; 664 665 iSize = 0; 666 667 /* If the edit control already exists, try to save its content */ 668 if (Globals.hEdit != NULL) 669 { 670 /* number of chars currently written into the editor. */ 671 iSize = GetWindowTextLength(Globals.hEdit); 672 if (iSize) 673 { 674 /* Allocates temporary buffer. */ 675 pTemp = HeapAlloc(GetProcessHeap(), 0, (iSize + 1) * sizeof(TCHAR)); 676 if (!pTemp) 677 { 678 ShowLastError(); 679 return; 680 } 681 682 /* Recover the text into the control. */ 683 GetWindowText(Globals.hEdit, pTemp, iSize + 1); 684 685 if (SendMessage(Globals.hEdit, EM_GETMODIFY, 0, 0)) 686 bModified = TRUE; 687 } 688 689 /* Restore original window procedure */ 690 SetWindowLongPtr(Globals.hEdit, GWLP_WNDPROC, (LONG_PTR)Globals.EditProc); 691 692 /* Destroy the edit control */ 693 DestroyWindow(Globals.hEdit); 694 } 695 696 /* Update wrap status into the main menu and recover style flags */ 697 dwStyle = (Globals.bWrapLongLines ? EDIT_STYLE_WRAP : EDIT_STYLE); 698 699 /* Create the new edit control */ 700 Globals.hEdit = CreateWindowEx(WS_EX_CLIENTEDGE, 701 EDIT_CLASS, 702 NULL, 703 dwStyle, 704 CW_USEDEFAULT, 705 CW_USEDEFAULT, 706 CW_USEDEFAULT, 707 CW_USEDEFAULT, 708 Globals.hMainWnd, 709 NULL, 710 Globals.hInstance, 711 NULL); 712 if (Globals.hEdit == NULL) 713 { 714 if (pTemp) 715 { 716 HeapFree(GetProcessHeap(), 0, pTemp); 717 } 718 719 ShowLastError(); 720 return; 721 } 722 723 SendMessage(Globals.hEdit, WM_SETFONT, (WPARAM)Globals.hFont, FALSE); 724 SendMessage(Globals.hEdit, EM_LIMITTEXT, 0, 0); 725 726 /* If some text was previously saved, restore it. */ 727 if (iSize != 0) 728 { 729 SetWindowText(Globals.hEdit, pTemp); 730 HeapFree(GetProcessHeap(), 0, pTemp); 731 732 if (bModified) 733 SendMessage(Globals.hEdit, EM_SETMODIFY, TRUE, 0); 734 } 735 736 /* Sub-class a new window callback for row/column detection. */ 737 Globals.EditProc = (WNDPROC)SetWindowLongPtr(Globals.hEdit, 738 GWLP_WNDPROC, 739 (LONG_PTR)EDIT_WndProc); 740 741 /* Finally shows new edit control and set focus into it. */ 742 ShowWindow(Globals.hEdit, SW_SHOW); 743 SetFocus(Globals.hEdit); 744 745 /* Re-arrange controls */ 746 PostMessageW(Globals.hMainWnd, WM_SIZE, 0, 0); 747 } 748 749 VOID DIALOG_EditWrap(VOID) 750 { 751 Globals.bWrapLongLines = !Globals.bWrapLongLines; 752 753 EnableMenuItem(Globals.hMenu, CMD_GOTO, (Globals.bWrapLongLines ? MF_GRAYED : MF_ENABLED)); 754 755 DoCreateEditWindow(); 756 DoShowHideStatusBar(); 757 } 758 759 VOID DIALOG_SelectFont(VOID) 760 { 761 CHOOSEFONT cf; 762 LOGFONT lf = Globals.lfFont; 763 764 ZeroMemory( &cf, sizeof(cf) ); 765 cf.lStructSize = sizeof(cf); 766 cf.hwndOwner = Globals.hMainWnd; 767 cf.lpLogFont = &lf; 768 cf.Flags = CF_SCREENFONTS | CF_INITTOLOGFONTSTRUCT | CF_NOVERTFONTS; 769 770 if (ChooseFont(&cf)) 771 { 772 HFONT currfont = Globals.hFont; 773 774 Globals.hFont = CreateFontIndirect(&lf); 775 Globals.lfFont = lf; 776 SendMessage(Globals.hEdit, WM_SETFONT, (WPARAM)Globals.hFont, TRUE); 777 if (currfont != NULL) 778 DeleteObject(currfont); 779 } 780 } 781 782 typedef HWND (WINAPI *FINDPROC)(LPFINDREPLACE lpfr); 783 784 static VOID DIALOG_SearchDialog(FINDPROC pfnProc) 785 { 786 if (Globals.hFindReplaceDlg != NULL) 787 { 788 SetFocus(Globals.hFindReplaceDlg); 789 return; 790 } 791 792 if (!Globals.find.lpstrFindWhat) 793 { 794 ZeroMemory(&Globals.find, sizeof(Globals.find)); 795 Globals.find.lStructSize = sizeof(Globals.find); 796 Globals.find.hwndOwner = Globals.hMainWnd; 797 Globals.find.lpstrFindWhat = Globals.szFindText; 798 Globals.find.wFindWhatLen = _countof(Globals.szFindText); 799 Globals.find.lpstrReplaceWith = Globals.szReplaceText; 800 Globals.find.wReplaceWithLen = _countof(Globals.szReplaceText); 801 Globals.find.Flags = FR_DOWN; 802 } 803 804 /* We only need to create the modal FindReplace dialog which will */ 805 /* notify us of incoming events using hMainWnd Window Messages */ 806 807 Globals.hFindReplaceDlg = pfnProc(&Globals.find); 808 assert(Globals.hFindReplaceDlg != NULL); 809 } 810 811 VOID DIALOG_Search(VOID) 812 { 813 DIALOG_SearchDialog(FindText); 814 } 815 816 VOID DIALOG_SearchNext(BOOL bDown) 817 { 818 if (bDown) 819 Globals.find.Flags |= FR_DOWN; 820 else 821 Globals.find.Flags &= ~FR_DOWN; 822 823 if (Globals.find.lpstrFindWhat != NULL && *Globals.find.lpstrFindWhat) 824 NOTEPAD_FindNext(&Globals.find, FALSE, TRUE); 825 else 826 DIALOG_Search(); 827 } 828 829 VOID DIALOG_Replace(VOID) 830 { 831 DIALOG_SearchDialog(ReplaceText); 832 } 833 834 typedef struct tagGOTO_DATA 835 { 836 UINT iLine; 837 UINT cLines; 838 } GOTO_DATA, *PGOTO_DATA; 839 840 static INT_PTR 841 CALLBACK 842 DIALOG_GoTo_DialogProc(HWND hwndDialog, UINT uMsg, WPARAM wParam, LPARAM lParam) 843 { 844 static PGOTO_DATA s_pGotoData; 845 846 switch (uMsg) 847 { 848 case WM_INITDIALOG: 849 s_pGotoData = (PGOTO_DATA)lParam; 850 SetDlgItemInt(hwndDialog, ID_LINENUMBER, s_pGotoData->iLine, FALSE); 851 return TRUE; /* Set focus */ 852 853 case WM_COMMAND: 854 { 855 if (LOWORD(wParam) == IDOK) 856 { 857 UINT iLine = GetDlgItemInt(hwndDialog, ID_LINENUMBER, NULL, FALSE); 858 if (iLine <= 0 || s_pGotoData->cLines < iLine) /* Out of range */ 859 { 860 /* Show error message */ 861 WCHAR title[128], text[256]; 862 LoadStringW(Globals.hInstance, STRING_NOTEPAD, title, _countof(title)); 863 LoadStringW(Globals.hInstance, STRING_LINE_NUMBER_OUT_OF_RANGE, text, _countof(text)); 864 MessageBoxW(hwndDialog, text, title, MB_OK); 865 866 SendDlgItemMessageW(hwndDialog, ID_LINENUMBER, EM_SETSEL, 0, -1); 867 SetFocus(GetDlgItem(hwndDialog, ID_LINENUMBER)); 868 break; 869 } 870 s_pGotoData->iLine = iLine; 871 EndDialog(hwndDialog, IDOK); 872 } 873 else if (LOWORD(wParam) == IDCANCEL) 874 { 875 EndDialog(hwndDialog, IDCANCEL); 876 } 877 break; 878 } 879 } 880 881 return 0; 882 } 883 884 VOID DIALOG_GoTo(VOID) 885 { 886 GOTO_DATA GotoData; 887 DWORD dwStart = 0, dwEnd = 0; 888 INT ich, cch = GetWindowTextLength(Globals.hEdit); 889 890 /* Get the current line number and the total line number */ 891 SendMessage(Globals.hEdit, EM_GETSEL, (WPARAM) &dwStart, (LPARAM) &dwEnd); 892 GotoData.iLine = (UINT)SendMessage(Globals.hEdit, EM_LINEFROMCHAR, dwStart, 0) + 1; 893 GotoData.cLines = (UINT)SendMessage(Globals.hEdit, EM_GETLINECOUNT, 0, 0); 894 895 /* Ask the user for line number */ 896 if (DialogBoxParam(Globals.hInstance, 897 MAKEINTRESOURCE(DIALOG_GOTO), 898 Globals.hMainWnd, 899 DIALOG_GoTo_DialogProc, 900 (LPARAM)&GotoData) != IDOK) 901 { 902 return; /* Canceled */ 903 } 904 905 --GotoData.iLine; /* Make it zero-based */ 906 907 /* Get ich (the target character index) from line number */ 908 if (GotoData.iLine <= 0) 909 ich = 0; 910 else if (GotoData.iLine >= GotoData.cLines) 911 ich = cch; 912 else 913 ich = (INT)SendMessage(Globals.hEdit, EM_LINEINDEX, GotoData.iLine, 0); 914 915 /* EM_LINEINDEX can return -1 on failure */ 916 if (ich < 0) 917 ich = 0; 918 919 /* Move the caret */ 920 SendMessage(Globals.hEdit, EM_SETSEL, ich, ich); 921 SendMessage(Globals.hEdit, EM_SCROLLCARET, 0, 0); 922 } 923 924 VOID DIALOG_StatusBarUpdateCaretPos(VOID) 925 { 926 int line, ich, col; 927 TCHAR buff[MAX_PATH]; 928 DWORD dwStart, dwSize; 929 930 SendMessage(Globals.hEdit, EM_GETSEL, (WPARAM)&dwStart, (LPARAM)&dwSize); 931 line = (int)SendMessage(Globals.hEdit, EM_LINEFROMCHAR, (WPARAM)dwStart, 0); 932 ich = (int)SendMessage(Globals.hEdit, EM_LINEINDEX, (WPARAM)line, 0); 933 934 /* EM_LINEINDEX can return -1 on failure */ 935 col = ((ich < 0) ? 0 : (dwStart - ich)); 936 937 StringCchPrintf(buff, _countof(buff), Globals.szStatusBarLineCol, line + 1, col + 1); 938 SendMessage(Globals.hStatusBar, SB_SETTEXT, SBPART_CURPOS, (LPARAM)buff); 939 } 940 941 VOID DIALOG_ViewStatusBar(VOID) 942 { 943 Globals.bShowStatusBar = !Globals.bShowStatusBar; 944 DoShowHideStatusBar(); 945 } 946 947 VOID DIALOG_HelpContents(VOID) 948 { 949 WinHelp(Globals.hMainWnd, helpfile, HELP_INDEX, 0); 950 } 951 952 VOID DIALOG_HelpAboutNotepad(VOID) 953 { 954 TCHAR szNotepad[MAX_STRING_LEN]; 955 TCHAR szNotepadAuthors[MAX_STRING_LEN]; 956 957 LoadString(Globals.hInstance, STRING_NOTEPAD, szNotepad, _countof(szNotepad)); 958 LoadString(Globals.hInstance, STRING_NOTEPAD_AUTHORS, szNotepadAuthors, _countof(szNotepadAuthors)); 959 960 ShellAbout(Globals.hMainWnd, szNotepad, szNotepadAuthors, 961 LoadIcon(Globals.hInstance, MAKEINTRESOURCE(IDI_NPICON))); 962 } 963