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