xref: /reactos/base/applications/notepad/main.c (revision 14d3b53c)
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 2000 Mike McCormack <Mike_McCormack@looksmart.com.au>
6  *             Copyright 1997,98 Marcel Baur <mbaur@g26.ethz.ch>
7  *             Copyright 2002 Sylvain Petreolle <spetreolle@yahoo.fr>
8  *             Copyright 2002 Andriy Palamarchuk
9  *             Copyright 2020-2023 Katayama Hirofumi MZ
10  */
11 
12 #include "notepad.h"
13 
14 #include <shlobj.h>
15 #include <strsafe.h>
16 
17 NOTEPAD_GLOBALS Globals;
18 static ATOM aFINDMSGSTRING;
19 
20 VOID NOTEPAD_EnableSearchMenu()
21 {
22     BOOL bEmpty = (GetWindowTextLengthW(Globals.hEdit) == 0);
23     UINT uEnable = MF_BYCOMMAND | (bEmpty ? MF_GRAYED : MF_ENABLED);
24     EnableMenuItem(Globals.hMenu, CMD_SEARCH, uEnable);
25     EnableMenuItem(Globals.hMenu, CMD_SEARCH_NEXT, uEnable);
26     EnableMenuItem(Globals.hMenu, CMD_SEARCH_PREV, uEnable);
27 }
28 
29 /***********************************************************************
30  *           SetFileName
31  *
32  *  Sets Global File Name.
33  */
34 VOID SetFileName(LPCTSTR szFileName)
35 {
36     StringCchCopy(Globals.szFileName, _countof(Globals.szFileName), szFileName);
37     Globals.szFileTitle[0] = 0;
38     GetFileTitle(szFileName, Globals.szFileTitle, _countof(Globals.szFileTitle));
39 
40     if (szFileName && szFileName[0])
41         SHAddToRecentDocs(SHARD_PATHW, szFileName);
42 }
43 
44 /***********************************************************************
45  *           NOTEPAD_MenuCommand
46  *
47  *  All handling of main menu events
48  */
49 static int NOTEPAD_MenuCommand(WPARAM wParam)
50 {
51     switch (wParam)
52     {
53     case CMD_NEW:        DIALOG_FileNew(); break;
54     case CMD_NEW_WINDOW: DIALOG_FileNewWindow(); break;
55     case CMD_OPEN:       DIALOG_FileOpen(); break;
56     case CMD_SAVE:       DIALOG_FileSave(); break;
57     case CMD_SAVE_AS:    DIALOG_FileSaveAs(); break;
58     case CMD_PRINT:      DIALOG_FilePrint(); break;
59     case CMD_PAGE_SETUP: DIALOG_FilePageSetup(); break;
60     case CMD_EXIT:       DIALOG_FileExit(); break;
61 
62     case CMD_UNDO:       DIALOG_EditUndo(); break;
63     case CMD_CUT:        DIALOG_EditCut(); break;
64     case CMD_COPY:       DIALOG_EditCopy(); break;
65     case CMD_PASTE:      DIALOG_EditPaste(); break;
66     case CMD_DELETE:     DIALOG_EditDelete(); break;
67     case CMD_SELECT_ALL: DIALOG_EditSelectAll(); break;
68     case CMD_TIME_DATE:  DIALOG_EditTimeDate(); break;
69 
70     case CMD_SEARCH:      DIALOG_Search(); break;
71     case CMD_SEARCH_NEXT: DIALOG_SearchNext(TRUE); break;
72     case CMD_REPLACE:     DIALOG_Replace(); break;
73     case CMD_GOTO:        DIALOG_GoTo(); break;
74     case CMD_SEARCH_PREV: DIALOG_SearchNext(FALSE); break;
75 
76     case CMD_WRAP: DIALOG_EditWrap(); break;
77     case CMD_FONT: DIALOG_SelectFont(); break;
78 
79     case CMD_STATUSBAR: DIALOG_ViewStatusBar(); break;
80 
81     case CMD_HELP_CONTENTS: DIALOG_HelpContents(); break;
82     case CMD_HELP_ABOUT_NOTEPAD: DIALOG_HelpAboutNotepad(); break;
83 
84     default:
85         break;
86     }
87     return 0;
88 }
89 
90 /***********************************************************************
91  *           NOTEPAD_FindTextAt
92  */
93 static BOOL
94 NOTEPAD_FindTextAt(FINDREPLACE *pFindReplace, LPCTSTR pszText, INT iTextLength, DWORD dwPosition)
95 {
96     BOOL bMatches;
97     size_t iTargetLength;
98     LPCTSTR pchPosition;
99 
100     if (!pFindReplace || !pszText)
101         return FALSE;
102 
103     iTargetLength = _tcslen(pFindReplace->lpstrFindWhat);
104     pchPosition = &pszText[dwPosition];
105 
106     /* Make proper comparison */
107     if (pFindReplace->Flags & FR_MATCHCASE)
108         bMatches = !_tcsncmp(pchPosition, pFindReplace->lpstrFindWhat, iTargetLength);
109     else
110         bMatches = !_tcsnicmp(pchPosition, pFindReplace->lpstrFindWhat, iTargetLength);
111 
112     if (bMatches && (pFindReplace->Flags & FR_WHOLEWORD))
113     {
114         if (dwPosition > 0)
115         {
116             if (_istalnum(*(pchPosition - 1)) || *(pchPosition - 1) == _T('_'))
117                 bMatches = FALSE;
118         }
119         if ((INT)dwPosition + iTargetLength < iTextLength)
120         {
121             if (_istalnum(pchPosition[iTargetLength]) || pchPosition[iTargetLength] == _T('_'))
122                 bMatches = FALSE;
123         }
124     }
125 
126     return bMatches;
127 }
128 
129 /***********************************************************************
130  *           NOTEPAD_FindNext
131  */
132 BOOL NOTEPAD_FindNext(FINDREPLACE *pFindReplace, BOOL bReplace, BOOL bShowAlert)
133 {
134     int iTextLength, iTargetLength;
135     size_t iAdjustment = 0;
136     LPTSTR pszText = NULL;
137     DWORD dwPosition, dwBegin, dwEnd;
138     BOOL bMatches = FALSE;
139     TCHAR szResource[128], szText[128];
140     BOOL bSuccess;
141 
142     iTargetLength = (int) _tcslen(pFindReplace->lpstrFindWhat);
143 
144     /* Retrieve the window text */
145     iTextLength = GetWindowTextLength(Globals.hEdit);
146     if (iTextLength > 0)
147     {
148         pszText = (LPTSTR) HeapAlloc(GetProcessHeap(), 0, (iTextLength + 1) * sizeof(TCHAR));
149         if (!pszText)
150             return FALSE;
151 
152         GetWindowText(Globals.hEdit, pszText, iTextLength + 1);
153     }
154 
155     SendMessage(Globals.hEdit, EM_GETSEL, (WPARAM) &dwBegin, (LPARAM) &dwEnd);
156     if (bReplace && ((dwEnd - dwBegin) == (DWORD) iTargetLength))
157     {
158         if (NOTEPAD_FindTextAt(pFindReplace, pszText, iTextLength, dwBegin))
159         {
160             SendMessage(Globals.hEdit, EM_REPLACESEL, TRUE, (LPARAM) pFindReplace->lpstrReplaceWith);
161             iAdjustment = _tcslen(pFindReplace->lpstrReplaceWith) - (dwEnd - dwBegin);
162         }
163     }
164 
165     if (pFindReplace->Flags & FR_DOWN)
166     {
167         /* Find Down */
168         dwPosition = dwEnd;
169         while(dwPosition < (DWORD) iTextLength)
170         {
171             bMatches = NOTEPAD_FindTextAt(pFindReplace, pszText, iTextLength, dwPosition);
172             if (bMatches)
173                 break;
174             dwPosition++;
175         }
176     }
177     else
178     {
179         /* Find Up */
180         dwPosition = dwBegin;
181         while(dwPosition > 0)
182         {
183             dwPosition--;
184             bMatches = NOTEPAD_FindTextAt(pFindReplace, pszText, iTextLength, dwPosition);
185             if (bMatches)
186                 break;
187         }
188     }
189 
190     if (bMatches)
191     {
192         /* Found target */
193         if (dwPosition > dwBegin)
194             dwPosition += (DWORD) iAdjustment;
195         SendMessage(Globals.hEdit, EM_SETSEL, dwPosition, dwPosition + iTargetLength);
196         SendMessage(Globals.hEdit, EM_SCROLLCARET, 0, 0);
197         bSuccess = TRUE;
198     }
199     else
200     {
201         /* Can't find target */
202         if (bShowAlert)
203         {
204             LoadString(Globals.hInstance, STRING_CANNOTFIND, szResource, _countof(szResource));
205             StringCchPrintf(szText, _countof(szText), szResource, pFindReplace->lpstrFindWhat);
206             LoadString(Globals.hInstance, STRING_NOTEPAD, szResource, _countof(szResource));
207             MessageBox(Globals.hFindReplaceDlg, szText, szResource, MB_OK);
208         }
209         bSuccess = FALSE;
210     }
211 
212     if (pszText)
213         HeapFree(GetProcessHeap(), 0, pszText);
214     return bSuccess;
215 }
216 
217 /***********************************************************************
218  *           NOTEPAD_ReplaceAll
219  */
220 static VOID NOTEPAD_ReplaceAll(FINDREPLACE *pFindReplace)
221 {
222     BOOL bShowAlert = TRUE;
223 
224     SendMessage(Globals.hEdit, EM_SETSEL, 0, 0);
225 
226     while (NOTEPAD_FindNext(pFindReplace, TRUE, bShowAlert))
227     {
228         bShowAlert = FALSE;
229     }
230 }
231 
232 /***********************************************************************
233  *           NOTEPAD_FindTerm
234  */
235 static VOID NOTEPAD_FindTerm(VOID)
236 {
237     Globals.hFindReplaceDlg = NULL;
238 }
239 
240 /***********************************************************************
241  * Data Initialization
242  */
243 static VOID NOTEPAD_InitData(HINSTANCE hInstance)
244 {
245     LPTSTR p;
246     static const TCHAR txt_files[] = _T("*.txt");
247     static const TCHAR all_files[] = _T("*.*");
248 
249     ZeroMemory(&Globals, sizeof(Globals));
250     Globals.hInstance = hInstance;
251     Globals.encFile = ENCODING_DEFAULT;
252 
253     p = Globals.szFilter;
254     p += LoadString(Globals.hInstance, STRING_TEXT_FILES_TXT, p, MAX_STRING_LEN) + 1;
255     _tcscpy(p, txt_files);
256     p += _countof(txt_files);
257 
258     p += LoadString(Globals.hInstance, STRING_ALL_FILES, p, MAX_STRING_LEN) + 1;
259     _tcscpy(p, all_files);
260     p += _countof(all_files);
261     *p = '\0';
262     Globals.find.lpstrFindWhat = NULL;
263 
264     Globals.hDevMode = NULL;
265     Globals.hDevNames = NULL;
266 }
267 
268 /***********************************************************************
269  * Enable/disable items on the menu based on control state
270  */
271 static VOID NOTEPAD_InitMenuPopup(HMENU menu, LPARAM index)
272 {
273     DWORD dwStart, dwEnd;
274     int enable;
275 
276     UNREFERENCED_PARAMETER(index);
277 
278     CheckMenuItem(menu, CMD_WRAP, (Globals.bWrapLongLines ? MF_CHECKED : MF_UNCHECKED));
279     CheckMenuItem(menu, CMD_STATUSBAR, (Globals.bShowStatusBar ? MF_CHECKED : MF_UNCHECKED));
280     EnableMenuItem(menu, CMD_UNDO,
281         SendMessage(Globals.hEdit, EM_CANUNDO, 0, 0) ? MF_ENABLED : MF_GRAYED);
282     EnableMenuItem(menu, CMD_PASTE,
283         IsClipboardFormatAvailable(CF_TEXT) ? MF_ENABLED : MF_GRAYED);
284     SendMessage(Globals.hEdit, EM_GETSEL, (WPARAM)&dwStart, (LPARAM)&dwEnd);
285     enable = ((dwStart == dwEnd) ? MF_GRAYED : MF_ENABLED);
286     EnableMenuItem(menu, CMD_CUT, enable);
287     EnableMenuItem(menu, CMD_COPY, enable);
288     EnableMenuItem(menu, CMD_DELETE, enable);
289 
290     EnableMenuItem(menu, CMD_SELECT_ALL,
291         GetWindowTextLength(Globals.hEdit) ? MF_ENABLED : MF_GRAYED);
292 }
293 
294 LRESULT CALLBACK EDIT_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
295 {
296     switch (msg)
297     {
298         case WM_KEYDOWN:
299         case WM_KEYUP:
300         {
301             switch (wParam)
302             {
303                 case VK_UP:
304                 case VK_DOWN:
305                 case VK_LEFT:
306                 case VK_RIGHT:
307                     DIALOG_StatusBarUpdateCaretPos();
308                     break;
309                 default:
310                 {
311                     UpdateWindowCaption(FALSE);
312                     break;
313                 }
314             }
315         }
316         case WM_LBUTTONUP:
317         {
318             DIALOG_StatusBarUpdateCaretPos();
319             break;
320         }
321     }
322     return CallWindowProc( Globals.EditProc, hWnd, msg, wParam, lParam);
323 }
324 
325 /***********************************************************************
326  *           NOTEPAD_WndProc
327  */
328 static LRESULT
329 WINAPI
330 NOTEPAD_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
331 {
332     switch (msg)
333     {
334 
335     case WM_CREATE:
336         Globals.hMainWnd = hWnd;
337         Globals.hMenu = GetMenu(hWnd);
338 
339         DragAcceptFiles(hWnd, TRUE); /* Accept Drag & Drop */
340 
341         /* Create controls */
342         DoCreateEditWindow();
343         DoShowHideStatusBar();
344 
345         DIALOG_FileNew(); /* Initialize file info */
346 
347         // For now, the "Help" dialog is disabled due to the lack of HTML Help support
348         EnableMenuItem(Globals.hMenu, CMD_HELP_CONTENTS, MF_BYCOMMAND | MF_GRAYED);
349         break;
350 
351     case WM_COMMAND:
352         if (HIWORD(wParam) == EN_CHANGE || HIWORD(wParam) == EN_HSCROLL || HIWORD(wParam) == EN_VSCROLL)
353             DIALOG_StatusBarUpdateCaretPos();
354         if ((HIWORD(wParam) == EN_CHANGE))
355             NOTEPAD_EnableSearchMenu();
356         NOTEPAD_MenuCommand(LOWORD(wParam));
357         break;
358 
359     case WM_CLOSE:
360         if (DoCloseFile())
361             DestroyWindow(hWnd);
362         break;
363 
364     case WM_QUERYENDSESSION:
365         if (DoCloseFile()) {
366             return 1;
367         }
368         break;
369 
370     case WM_DESTROY:
371         if (Globals.hFont)
372             DeleteObject(Globals.hFont);
373         if (Globals.hDevMode)
374             GlobalFree(Globals.hDevMode);
375         if (Globals.hDevNames)
376             GlobalFree(Globals.hDevNames);
377         SetWindowLongPtr(Globals.hEdit, GWLP_WNDPROC, (LONG_PTR)Globals.EditProc);
378         NOTEPAD_SaveSettingsToRegistry();
379         PostQuitMessage(0);
380         break;
381 
382     case WM_SIZE:
383     {
384         RECT rc;
385         GetClientRect(hWnd, &rc);
386 
387         if (Globals.bShowStatusBar)
388         {
389             RECT rcStatus;
390             SendMessageW(Globals.hStatusBar, WM_SIZE, 0, 0);
391             GetWindowRect(Globals.hStatusBar, &rcStatus);
392             rc.bottom -= rcStatus.bottom - rcStatus.top;
393         }
394 
395         MoveWindow(Globals.hEdit, 0, 0, rc.right, rc.bottom, TRUE);
396 
397         if (Globals.bShowStatusBar)
398         {
399             /* Align status bar parts, only if the status bar resize operation succeeds */
400             DIALOG_StatusBarAlignParts();
401         }
402         break;
403     }
404 
405     /* The entire client area is covered by edit control and by
406      * the status bar. So there is no need to erase main background.
407      * This resolves the horrible flicker effect during windows resizes. */
408     case WM_ERASEBKGND:
409         return 1;
410 
411     case WM_SETFOCUS:
412         SetFocus(Globals.hEdit);
413         break;
414 
415     case WM_DROPFILES:
416     {
417         TCHAR szFileName[MAX_PATH];
418         HDROP hDrop = (HDROP) wParam;
419 
420         DragQueryFile(hDrop, 0, szFileName, _countof(szFileName));
421         DragFinish(hDrop);
422         DoOpenFile(szFileName);
423         break;
424     }
425 
426     case WM_INITMENUPOPUP:
427         NOTEPAD_InitMenuPopup((HMENU)wParam, lParam);
428         break;
429 
430     default:
431         if (msg == aFINDMSGSTRING)
432         {
433             FINDREPLACE *pFindReplace = (FINDREPLACE *) lParam;
434             Globals.find = *(FINDREPLACE *) lParam;
435 
436             WaitCursor(TRUE);
437 
438             if (pFindReplace->Flags & FR_FINDNEXT)
439                 NOTEPAD_FindNext(pFindReplace, FALSE, TRUE);
440             else if (pFindReplace->Flags & FR_REPLACE)
441                 NOTEPAD_FindNext(pFindReplace, TRUE, TRUE);
442             else if (pFindReplace->Flags & FR_REPLACEALL)
443                 NOTEPAD_ReplaceAll(pFindReplace);
444             else if (pFindReplace->Flags & FR_DIALOGTERM)
445                 NOTEPAD_FindTerm();
446 
447             WaitCursor(FALSE);
448             break;
449         }
450 
451         return DefWindowProc(hWnd, msg, wParam, lParam);
452     }
453     return 0;
454 }
455 
456 static int AlertFileDoesNotExist(LPCTSTR szFileName)
457 {
458     return DIALOG_StringMsgBox(Globals.hMainWnd, STRING_DOESNOTEXIST,
459                                szFileName,
460                                MB_ICONEXCLAMATION | MB_YESNO);
461 }
462 
463 static BOOL HandleCommandLine(LPTSTR cmdline)
464 {
465     BOOL opt_print = FALSE;
466     TCHAR szPath[MAX_PATH];
467 
468     while (*cmdline == _T(' ') || *cmdline == _T('-') || *cmdline == _T('/'))
469     {
470         TCHAR option;
471 
472         if (*cmdline++ == _T(' ')) continue;
473 
474         option = *cmdline;
475         if (option) cmdline++;
476         while (*cmdline == _T(' ')) cmdline++;
477 
478         switch(option)
479         {
480             case 'p':
481             case 'P':
482                 opt_print = TRUE;
483                 break;
484         }
485     }
486 
487     if (*cmdline)
488     {
489         /* file name is passed in the command line */
490         LPCTSTR file_name = NULL;
491         BOOL file_exists = FALSE;
492         TCHAR buf[MAX_PATH];
493 
494         if (cmdline[0] == _T('"'))
495         {
496             cmdline++;
497             cmdline[lstrlen(cmdline) - 1] = 0;
498         }
499 
500         file_name = cmdline;
501         if (FileExists(file_name))
502         {
503             file_exists = TRUE;
504         }
505         else if (!HasFileExtension(cmdline))
506         {
507             static const TCHAR txt[] = _T(".txt");
508 
509             /* try to find file with ".txt" extension */
510             if (!_tcscmp(txt, cmdline + _tcslen(cmdline) - _tcslen(txt)))
511             {
512                 file_exists = FALSE;
513             }
514             else
515             {
516                 _tcsncpy(buf, cmdline, MAX_PATH - _tcslen(txt) - 1);
517                 _tcscat(buf, txt);
518                 file_name = buf;
519                 file_exists = FileExists(file_name);
520             }
521         }
522 
523         GetFullPathName(file_name, _countof(szPath), szPath, NULL);
524 
525         if (file_exists)
526         {
527             DoOpenFile(szPath);
528             InvalidateRect(Globals.hMainWnd, NULL, FALSE);
529             if (opt_print)
530             {
531                 DIALOG_FilePrint();
532                 return FALSE;
533             }
534         }
535         else
536         {
537             switch (AlertFileDoesNotExist(file_name))
538             {
539             case IDYES:
540                 DoOpenFile(szPath);
541                 break;
542 
543             case IDNO:
544                 break;
545             }
546         }
547     }
548 
549     return TRUE;
550 }
551 
552 /***********************************************************************
553  *           WinMain
554  */
555 int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE prev, LPTSTR cmdline, int show)
556 {
557     MSG msg;
558     HACCEL hAccel;
559     WNDCLASSEX wndclass;
560     HMONITOR monitor;
561     MONITORINFO info;
562     INT x, y;
563     RECT rcIntersect;
564     static const TCHAR className[] = _T("Notepad");
565     static const TCHAR winName[] = _T("Notepad");
566 
567     switch (GetUserDefaultUILanguage())
568     {
569     case MAKELANGID(LANG_HEBREW, SUBLANG_DEFAULT):
570         SetProcessDefaultLayout(LAYOUT_RTL);
571         break;
572 
573     default:
574         break;
575     }
576 
577     UNREFERENCED_PARAMETER(prev);
578 
579     aFINDMSGSTRING = (ATOM)RegisterWindowMessage(FINDMSGSTRING);
580 
581     NOTEPAD_InitData(hInstance);
582     NOTEPAD_LoadSettingsFromRegistry();
583 
584     ZeroMemory(&wndclass, sizeof(wndclass));
585     wndclass.cbSize = sizeof(wndclass);
586     wndclass.lpfnWndProc = NOTEPAD_WndProc;
587     wndclass.hInstance = Globals.hInstance;
588     wndclass.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_NPICON));
589     wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
590     wndclass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
591     wndclass.lpszMenuName = MAKEINTRESOURCE(MAIN_MENU);
592     wndclass.lpszClassName = className;
593     wndclass.hIconSm = (HICON)LoadImage(hInstance,
594                                         MAKEINTRESOURCE(IDI_NPICON),
595                                         IMAGE_ICON,
596                                         GetSystemMetrics(SM_CXSMICON),
597                                         GetSystemMetrics(SM_CYSMICON),
598                                         0);
599     if (!RegisterClassEx(&wndclass))
600     {
601         ShowLastError();
602         return 1;
603     }
604 
605     /* Setup windows */
606 
607     monitor = MonitorFromRect(&Globals.main_rect, MONITOR_DEFAULTTOPRIMARY);
608     info.cbSize = sizeof(info);
609     GetMonitorInfoW(monitor, &info);
610 
611     x = Globals.main_rect.left;
612     y = Globals.main_rect.top;
613     if (!IntersectRect(&rcIntersect, &Globals.main_rect, &info.rcWork))
614         x = y = CW_USEDEFAULT;
615 
616     /* Globals.hMainWnd will be set in WM_CREATE handling */
617     CreateWindow(className,
618                  winName,
619                  WS_OVERLAPPEDWINDOW,
620                  x,
621                  y,
622                  Globals.main_rect.right - Globals.main_rect.left,
623                  Globals.main_rect.bottom - Globals.main_rect.top,
624                  NULL,
625                  NULL,
626                  Globals.hInstance,
627                  NULL);
628     if (!Globals.hMainWnd)
629     {
630         ShowLastError();
631         return 1;
632     }
633 
634     ShowWindow(Globals.hMainWnd, show);
635     UpdateWindow(Globals.hMainWnd);
636 
637     if (!HandleCommandLine(cmdline))
638         return 0;
639 
640     hAccel = LoadAccelerators(hInstance, MAKEINTRESOURCE(ID_ACCEL));
641 
642     while (GetMessage(&msg, NULL, 0, 0))
643     {
644         if (!TranslateAccelerator(Globals.hMainWnd, hAccel, &msg) &&
645             !IsDialogMessage(Globals.hFindReplaceDlg, &msg))
646         {
647             TranslateMessage(&msg);
648             DispatchMessage(&msg);
649         }
650     }
651 
652     DestroyAcceleratorTable(hAccel);
653 
654     return (int) msg.wParam;
655 }
656