[CRT] Massively improve performance of rand_s
[reactos.git] / base / applications / notepad / dialog.c
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 }