xref: /reactos/base/applications/notepad/dialog.c (revision ac0bcf4a)
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)
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 = 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