xref: /reactos/base/applications/notepad/dialog.c (revision d9afe73d)
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 
ShowLastError(VOID)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  */
UpdateWindowCaption(BOOL clearModifyAlert)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 
WaitCursor(BOOL bBegin)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 
DIALOG_StatusBarAlignParts(VOID)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 
DIALOG_StatusBarUpdateLineEndings(VOID)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 
DIALOG_StatusBarUpdateEncoding(VOID)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 
DIALOG_StatusBarUpdateAll(VOID)186 static VOID DIALOG_StatusBarUpdateAll(VOID)
187 {
188     DIALOG_StatusBarUpdateCaretPos();
189     DIALOG_StatusBarUpdateLineEndings();
190     DIALOG_StatusBarUpdateEncoding();
191 }
192 
DIALOG_StringMsgBox(HWND hParent,int formatId,LPCTSTR szString,DWORD dwFlags)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 
AlertFileNotFound(LPCTSTR szFileName)214 static void AlertFileNotFound(LPCTSTR szFileName)
215 {
216     DIALOG_StringMsgBox(Globals.hMainWnd, STRING_NOTFOUND, szFileName, MB_ICONEXCLAMATION | MB_OK);
217 }
218 
AlertFileNotSaved(LPCTSTR szFileName)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  */
FileExists(LPCTSTR szFilename)235 BOOL FileExists(LPCTSTR szFilename)
236 {
237     return GetFileAttributes(szFilename) != INVALID_FILE_ATTRIBUTES;
238 }
239 
HasFileExtension(LPCTSTR szFilename)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 
DoSaveFile(VOID)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  */
DoCloseFile(VOID)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 
DoOpenFile(LPCTSTR szFileName)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 
DIALOG_FileNew(VOID)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 
DIALOG_FileNewWindow(VOID)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 
DIALOG_FileOpen(VOID)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 
DIALOG_FileSave(VOID)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
DIALOG_FileSaveAs_Hook(HWND hDlg,UINT msg,WPARAM wParam,LPARAM lParam)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 
DIALOG_FileSaveAs(VOID)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 
DIALOG_FileExit(VOID)580 VOID DIALOG_FileExit(VOID)
581 {
582     PostMessage(Globals.hMainWnd, WM_CLOSE, 0, 0);
583 }
584 
DIALOG_EditUndo(VOID)585 VOID DIALOG_EditUndo(VOID)
586 {
587     SendMessage(Globals.hEdit, EM_UNDO, 0, 0);
588 }
589 
DIALOG_EditCut(VOID)590 VOID DIALOG_EditCut(VOID)
591 {
592     SendMessage(Globals.hEdit, WM_CUT, 0, 0);
593 }
594 
DIALOG_EditCopy(VOID)595 VOID DIALOG_EditCopy(VOID)
596 {
597     SendMessage(Globals.hEdit, WM_COPY, 0, 0);
598 }
599 
DIALOG_EditPaste(VOID)600 VOID DIALOG_EditPaste(VOID)
601 {
602     SendMessage(Globals.hEdit, WM_PASTE, 0, 0);
603 }
604 
DIALOG_EditDelete(VOID)605 VOID DIALOG_EditDelete(VOID)
606 {
607     SendMessage(Globals.hEdit, WM_CLEAR, 0, 0);
608 }
609 
DIALOG_EditSelectAll(VOID)610 VOID DIALOG_EditSelectAll(VOID)
611 {
612     SendMessage(Globals.hEdit, EM_SETSEL, 0, -1);
613 }
614 
DIALOG_EditTimeDate(VOID)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 
DoShowHideStatusBar(VOID)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 
DoCreateEditWindow(VOID)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 
DIALOG_EditWrap(VOID)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 
DIALOG_SelectFont(VOID)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 
DIALOG_SearchDialog(FINDPROC pfnProc)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 
DIALOG_Search(VOID)818 VOID DIALOG_Search(VOID)
819 {
820     DIALOG_SearchDialog(FindText);
821 }
822 
DIALOG_SearchNext(BOOL bDown)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 
DIALOG_Replace(VOID)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
DIALOG_GoTo_DialogProc(HWND hwndDialog,UINT uMsg,WPARAM wParam,LPARAM lParam)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 
DIALOG_GoTo(VOID)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 
DIALOG_StatusBarUpdateCaretPos(VOID)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 
DIALOG_ViewStatusBar(VOID)948 VOID DIALOG_ViewStatusBar(VOID)
949 {
950     Globals.bShowStatusBar = !Globals.bShowStatusBar;
951     DoShowHideStatusBar();
952 }
953 
DIALOG_HelpContents(VOID)954 VOID DIALOG_HelpContents(VOID)
955 {
956     WinHelp(Globals.hMainWnd, helpfile, HELP_INDEX, 0);
957 }
958 
DIALOG_HelpAboutNotepad(VOID)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