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