combase: Implement WindowsTrimStringStart.
[wine.git] / programs / notepad / dialog.c
blob1ea2ef90c33df63aa06a3afe7b4757eae5bb4a1f
1 /*
2 * Notepad (dialog.c)
4 * Copyright 1998,99 Marcel Baur <mbaur@g26.ethz.ch>
5 * Copyright 2002 Sylvain Petreolle <spetreolle@yahoo.fr>
6 * Copyright 2002 Andriy Palamarchuk
7 * Copyright 2007 Rolf Kalbermatter
8 * Copyright 2010 Vitaly Perov
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public
12 * License as published by the Free Software Foundation; either
13 * version 2.1 of the License, or (at your option) any later version.
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with this library; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
25 #include <assert.h>
26 #include <stdio.h>
27 #include <windows.h>
28 #include <shellapi.h>
29 #include <commdlg.h>
30 #include <shlwapi.h>
31 #include <winternl.h>
33 #include "main.h"
34 #include "dialog.h"
36 #define SPACES_IN_TAB 8
37 #define PRINT_LEN_MAX 500
39 static const WCHAR helpfileW[] = { 'n','o','t','e','p','a','d','.','h','l','p',0 };
41 static INT_PTR WINAPI DIALOG_PAGESETUP_DlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam);
43 /* Swap bytes of WCHAR buffer (big-endian <-> little-endian). */
44 static inline void byteswap_wide_string(LPWSTR str, UINT num)
46 UINT i;
47 for (i = 0; i < num; i++) str[i] = RtlUshortByteSwap(str[i]);
50 static void load_encoding_name(ENCODING enc, WCHAR* buffer, int length)
52 switch (enc)
54 case ENCODING_UTF16LE:
55 LoadStringW(Globals.hInstance, STRING_UNICODE_LE, buffer, length);
56 break;
58 case ENCODING_UTF16BE:
59 LoadStringW(Globals.hInstance, STRING_UNICODE_BE, buffer, length);
60 break;
62 case ENCODING_UTF8:
63 LoadStringW(Globals.hInstance, STRING_UTF8, buffer, length);
64 break;
66 case ENCODING_ANSI:
68 CPINFOEXW cpi;
69 GetCPInfoExW(CP_ACP, 0, &cpi);
70 lstrcpynW(buffer, cpi.CodePageName, length);
71 break;
74 default:
75 assert(0 && "bad encoding in load_encoding_name");
76 break;
80 VOID ShowLastError(void)
82 DWORD error = GetLastError();
83 if (error != NO_ERROR)
85 LPWSTR lpMsgBuf;
86 WCHAR szTitle[MAX_STRING_LEN];
88 LoadStringW(Globals.hInstance, STRING_ERROR, szTitle, ARRAY_SIZE(szTitle));
89 FormatMessageW(
90 FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
91 NULL, error, 0, (LPWSTR)&lpMsgBuf, 0, NULL);
92 MessageBoxW(NULL, lpMsgBuf, szTitle, MB_OK | MB_ICONERROR);
93 LocalFree(lpMsgBuf);
97 /**
98 * Sets the caption of the main window according to Globals.szFileTitle:
99 * Untitled - Notepad if no file is open
100 * filename - Notepad if a file is given
102 void UpdateWindowCaption(void)
104 WCHAR szCaption[MAX_STRING_LEN];
105 WCHAR szNotepad[MAX_STRING_LEN];
106 static const WCHAR hyphenW[] = { ' ','-',' ',0 };
108 if (Globals.szFileTitle[0] != '\0')
109 lstrcpyW(szCaption, Globals.szFileTitle);
110 else
111 LoadStringW(Globals.hInstance, STRING_UNTITLED, szCaption, ARRAY_SIZE(szCaption));
113 LoadStringW(Globals.hInstance, STRING_NOTEPAD, szNotepad, ARRAY_SIZE(szNotepad));
114 lstrcatW(szCaption, hyphenW);
115 lstrcatW(szCaption, szNotepad);
117 SetWindowTextW(Globals.hMainWnd, szCaption);
120 int DIALOG_StringMsgBox(HWND hParent, int formatId, LPCWSTR szString, DWORD dwFlags)
122 WCHAR szMessage[MAX_STRING_LEN];
123 WCHAR szResource[MAX_STRING_LEN];
125 /* Load and format szMessage */
126 LoadStringW(Globals.hInstance, formatId, szResource, ARRAY_SIZE(szResource));
127 wnsprintfW(szMessage, ARRAY_SIZE(szMessage), szResource, szString);
129 /* Load szCaption */
130 if ((dwFlags & MB_ICONMASK) == MB_ICONEXCLAMATION)
131 LoadStringW(Globals.hInstance, STRING_ERROR, szResource, ARRAY_SIZE(szResource));
132 else
133 LoadStringW(Globals.hInstance, STRING_NOTEPAD, szResource, ARRAY_SIZE(szResource));
135 /* Display Modal Dialog */
136 if (hParent == NULL)
137 hParent = Globals.hMainWnd;
138 return MessageBoxW(hParent, szMessage, szResource, dwFlags);
141 static void AlertFileNotFound(LPCWSTR szFileName)
143 DIALOG_StringMsgBox(NULL, STRING_NOTFOUND, szFileName, MB_ICONEXCLAMATION|MB_OK);
146 static int AlertFileNotSaved(LPCWSTR szFileName)
148 WCHAR szUntitled[MAX_STRING_LEN];
150 LoadStringW(Globals.hInstance, STRING_UNTITLED, szUntitled, ARRAY_SIZE(szUntitled));
151 return DIALOG_StringMsgBox(NULL, STRING_NOTSAVED, szFileName[0] ? szFileName : szUntitled,
152 MB_ICONQUESTION|MB_YESNOCANCEL);
155 static int AlertUnicodeCharactersLost(LPCWSTR szFileName)
157 WCHAR szCaption[MAX_STRING_LEN];
158 WCHAR szMsgFormat[MAX_STRING_LEN];
159 WCHAR szEnc[MAX_STRING_LEN];
160 WCHAR* szMsg;
161 DWORD_PTR args[2];
162 int rc;
164 LoadStringW(Globals.hInstance, STRING_NOTEPAD, szCaption,
165 ARRAY_SIZE(szCaption));
166 LoadStringW(Globals.hInstance, STRING_LOSS_OF_UNICODE_CHARACTERS,
167 szMsgFormat, ARRAY_SIZE(szMsgFormat));
168 load_encoding_name(ENCODING_ANSI, szEnc, ARRAY_SIZE(szEnc));
169 args[0] = (DWORD_PTR)szFileName;
170 args[1] = (DWORD_PTR)szEnc;
171 FormatMessageW(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_ARGUMENT_ARRAY, szMsgFormat, 0, 0, (LPWSTR)&szMsg, 0, (__ms_va_list*)args);
172 rc = MessageBoxW(Globals.hMainWnd, szMsg, szCaption,
173 MB_OKCANCEL|MB_ICONEXCLAMATION);
174 LocalFree(szMsg);
175 return rc;
179 * Returns:
180 * TRUE - if file exists
181 * FALSE - if file does not exist
183 BOOL FileExists(LPCWSTR szFilename)
185 WIN32_FIND_DATAW entry;
186 HANDLE hFile;
188 hFile = FindFirstFileW(szFilename, &entry);
189 FindClose(hFile);
191 return (hFile != INVALID_HANDLE_VALUE);
194 static inline BOOL is_conversion_to_ansi_lossy(LPCWSTR textW, int lenW)
196 BOOL ret = FALSE;
197 WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, textW, lenW, NULL, 0,
198 NULL, &ret);
199 return ret;
202 typedef enum
204 SAVED_OK,
205 SAVE_FAILED,
206 SHOW_SAVEAS_DIALOG
207 } SAVE_STATUS;
209 /* szFileName is the filename to save under; enc is the encoding to use.
211 * If the function succeeds, it returns SAVED_OK.
212 * If the function fails, it returns SAVE_FAILED.
213 * If Unicode data could be lost due to conversion to a non-Unicode character
214 * set, a warning is displayed. The user can continue (and the function carries
215 * on), or cancel (and the function returns SHOW_SAVEAS_DIALOG).
217 static SAVE_STATUS DoSaveFile(LPCWSTR szFileName, ENCODING enc)
219 int lenW;
220 WCHAR* textW;
221 HANDLE hFile;
222 DWORD dwNumWrite;
223 PVOID pBytes;
224 DWORD size;
226 /* lenW includes the byte-order mark, but not the \0. */
227 lenW = GetWindowTextLengthW(Globals.hEdit) + 1;
228 textW = HeapAlloc(GetProcessHeap(), 0, (lenW+1) * sizeof(WCHAR));
229 if (!textW)
231 ShowLastError();
232 return SAVE_FAILED;
234 textW[0] = (WCHAR) 0xfeff;
235 lenW = GetWindowTextW(Globals.hEdit, textW+1, lenW) + 1;
237 switch (enc)
239 case ENCODING_UTF16BE:
240 byteswap_wide_string(textW, lenW);
241 /* fall through */
243 case ENCODING_UTF16LE:
244 size = lenW * sizeof(WCHAR);
245 pBytes = textW;
246 break;
248 case ENCODING_UTF8:
249 size = WideCharToMultiByte(CP_UTF8, 0, textW, lenW, NULL, 0, NULL, NULL);
250 pBytes = HeapAlloc(GetProcessHeap(), 0, size);
251 if (!pBytes)
253 ShowLastError();
254 HeapFree(GetProcessHeap(), 0, textW);
255 return SAVE_FAILED;
257 WideCharToMultiByte(CP_UTF8, 0, textW, lenW, pBytes, size, NULL, NULL);
258 HeapFree(GetProcessHeap(), 0, textW);
259 break;
261 default:
262 if (is_conversion_to_ansi_lossy(textW+1, lenW-1)
263 && AlertUnicodeCharactersLost(szFileName) == IDCANCEL)
265 HeapFree(GetProcessHeap(), 0, textW);
266 return SHOW_SAVEAS_DIALOG;
269 size = WideCharToMultiByte(CP_ACP, 0, textW+1, lenW-1, NULL, 0, NULL, NULL);
270 pBytes = HeapAlloc(GetProcessHeap(), 0, size);
271 if (!pBytes)
273 ShowLastError();
274 HeapFree(GetProcessHeap(), 0, textW);
275 return SAVE_FAILED;
277 WideCharToMultiByte(CP_ACP, 0, textW+1, lenW-1, pBytes, size, NULL, NULL);
278 HeapFree(GetProcessHeap(), 0, textW);
279 break;
282 hFile = CreateFileW(szFileName, GENERIC_WRITE, FILE_SHARE_WRITE,
283 NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
284 if(hFile == INVALID_HANDLE_VALUE)
286 ShowLastError();
287 HeapFree(GetProcessHeap(), 0, pBytes);
288 return SAVE_FAILED;
290 if (!WriteFile(hFile, pBytes, size, &dwNumWrite, NULL))
292 ShowLastError();
293 CloseHandle(hFile);
294 HeapFree(GetProcessHeap(), 0, pBytes);
295 return SAVE_FAILED;
297 SetEndOfFile(hFile);
298 CloseHandle(hFile);
299 HeapFree(GetProcessHeap(), 0, pBytes);
301 SendMessageW(Globals.hEdit, EM_SETMODIFY, FALSE, 0);
302 return SAVED_OK;
306 * Returns:
307 * TRUE - User agreed to close (both save/don't save)
308 * FALSE - User cancelled close by selecting "Cancel"
310 BOOL DoCloseFile(void)
312 int nResult;
313 static const WCHAR empty_strW[] = { 0 };
315 nResult=GetWindowTextLengthW(Globals.hEdit);
316 if (SendMessageW(Globals.hEdit, EM_GETMODIFY, 0, 0) &&
317 (nResult || Globals.szFileName[0]))
319 /* prompt user to save changes */
320 nResult = AlertFileNotSaved(Globals.szFileName);
321 switch (nResult) {
322 case IDYES: return DIALOG_FileSave();
324 case IDNO: break;
326 case IDCANCEL: return(FALSE);
328 default: return(FALSE);
329 } /* switch */
330 } /* if */
332 SetFileNameAndEncoding(empty_strW, ENCODING_ANSI);
334 UpdateWindowCaption();
335 return(TRUE);
338 static inline ENCODING detect_encoding_of_buffer(const void* buffer, int size)
340 static const char bom_utf8[] = { 0xef, 0xbb, 0xbf };
341 if (size >= sizeof(bom_utf8) && !memcmp(buffer, bom_utf8, sizeof(bom_utf8)))
342 return ENCODING_UTF8;
343 else
345 int flags = IS_TEXT_UNICODE_SIGNATURE |
346 IS_TEXT_UNICODE_REVERSE_SIGNATURE |
347 IS_TEXT_UNICODE_ODD_LENGTH;
348 IsTextUnicode(buffer, size, &flags);
349 if (flags & IS_TEXT_UNICODE_SIGNATURE)
350 return ENCODING_UTF16LE;
351 else if (flags & IS_TEXT_UNICODE_REVERSE_SIGNATURE)
352 return ENCODING_UTF16BE;
353 else
354 return ENCODING_ANSI;
358 void DoOpenFile(LPCWSTR szFileName, ENCODING enc)
360 static const WCHAR dotlog[] = { '.','L','O','G',0 };
361 HANDLE hFile;
362 LPSTR pTemp;
363 DWORD size;
364 DWORD dwNumRead;
365 int lenW;
366 WCHAR* textW;
367 int i;
368 WCHAR log[5];
370 /* Close any files and prompt to save changes */
371 if (!DoCloseFile())
372 return;
374 hFile = CreateFileW(szFileName, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
375 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
376 if(hFile == INVALID_HANDLE_VALUE)
378 AlertFileNotFound(szFileName);
379 return;
382 size = GetFileSize(hFile, NULL);
383 if (size == INVALID_FILE_SIZE)
385 CloseHandle(hFile);
386 ShowLastError();
387 return;
390 /* Extra memory for (WCHAR)'\0'-termination. */
391 pTemp = HeapAlloc(GetProcessHeap(), 0, size+2);
392 if (!pTemp)
394 CloseHandle(hFile);
395 ShowLastError();
396 return;
399 if (!ReadFile(hFile, pTemp, size, &dwNumRead, NULL))
401 CloseHandle(hFile);
402 HeapFree(GetProcessHeap(), 0, pTemp);
403 ShowLastError();
404 return;
407 CloseHandle(hFile);
409 size = dwNumRead;
411 if (enc == ENCODING_AUTO)
412 enc = detect_encoding_of_buffer(pTemp, size);
413 else if (size >= 2 && (enc==ENCODING_UTF16LE || enc==ENCODING_UTF16BE))
415 /* If UTF-16 (BE or LE) is selected, and there is a UTF-16 BOM,
416 * override the selection (like native Notepad).
418 if ((BYTE)pTemp[0] == 0xff && (BYTE)pTemp[1] == 0xfe)
419 enc = ENCODING_UTF16LE;
420 else if ((BYTE)pTemp[0] == 0xfe && (BYTE)pTemp[1] == 0xff)
421 enc = ENCODING_UTF16BE;
424 switch (enc)
426 case ENCODING_UTF16BE:
427 byteswap_wide_string((WCHAR*) pTemp, size/sizeof(WCHAR));
428 /* Forget whether the file is BE or LE, like native Notepad. */
429 enc = ENCODING_UTF16LE;
431 /* fall through */
433 case ENCODING_UTF16LE:
434 textW = (LPWSTR)pTemp;
435 lenW = size/sizeof(WCHAR);
436 break;
438 default:
440 int cp = (enc==ENCODING_UTF8) ? CP_UTF8 : CP_ACP;
441 lenW = MultiByteToWideChar(cp, 0, pTemp, size, NULL, 0);
442 textW = HeapAlloc(GetProcessHeap(), 0, (lenW+1) * sizeof(WCHAR));
443 if (!textW)
445 ShowLastError();
446 HeapFree(GetProcessHeap(), 0, pTemp);
447 return;
449 MultiByteToWideChar(cp, 0, pTemp, size, textW, lenW);
450 HeapFree(GetProcessHeap(), 0, pTemp);
451 break;
455 /* Replace '\0's with spaces. Other than creating a custom control that
456 * can deal with '\0' characters, it's the best that can be done.
458 for (i = 0; i < lenW; i++)
459 if (textW[i] == '\0')
460 textW[i] = ' ';
461 textW[lenW] = '\0';
463 if (lenW >= 1 && textW[0] == 0xfeff)
464 SetWindowTextW(Globals.hEdit, textW+1);
465 else
466 SetWindowTextW(Globals.hEdit, textW);
468 HeapFree(GetProcessHeap(), 0, textW);
470 SendMessageW(Globals.hEdit, EM_SETMODIFY, FALSE, 0);
471 SendMessageW(Globals.hEdit, EM_EMPTYUNDOBUFFER, 0, 0);
472 SetFocus(Globals.hEdit);
474 /* If the file starts with .LOG, add a time/date at the end and set cursor after */
475 if (GetWindowTextW(Globals.hEdit, log, ARRAY_SIZE(log)) && !lstrcmpW(log, dotlog))
477 static const WCHAR lfW[] = { '\r','\n',0 };
478 SendMessageW(Globals.hEdit, EM_SETSEL, GetWindowTextLengthW(Globals.hEdit), -1);
479 SendMessageW(Globals.hEdit, EM_REPLACESEL, TRUE, (LPARAM)lfW);
480 DIALOG_EditTimeDate();
481 SendMessageW(Globals.hEdit, EM_REPLACESEL, TRUE, (LPARAM)lfW);
484 SetFileNameAndEncoding(szFileName, enc);
485 UpdateWindowCaption();
488 VOID DIALOG_FileNew(VOID)
490 static const WCHAR empty_strW[] = { 0 };
492 /* Close any files and prompt to save changes */
493 if (DoCloseFile()) {
494 SetWindowTextW(Globals.hEdit, empty_strW);
495 SendMessageW(Globals.hEdit, EM_EMPTYUNDOBUFFER, 0, 0);
496 SetFocus(Globals.hEdit);
500 /* Used to detect encoding of files selected in Open dialog.
501 * Returns ENCODING_AUTO if file can't be read, etc.
503 static ENCODING detect_encoding_of_file(LPCWSTR szFileName)
505 DWORD size;
506 HANDLE hFile = CreateFileW(szFileName, GENERIC_READ, FILE_SHARE_READ, NULL,
507 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
508 if (hFile == INVALID_HANDLE_VALUE)
509 return ENCODING_AUTO;
510 size = GetFileSize(hFile, NULL);
511 if (size == INVALID_FILE_SIZE)
513 CloseHandle(hFile);
514 return ENCODING_AUTO;
516 else
518 DWORD dwNumRead;
519 BYTE buffer[MAX_STRING_LEN];
520 if (!ReadFile(hFile, buffer, min(size, sizeof(buffer)), &dwNumRead, NULL))
522 CloseHandle(hFile);
523 return ENCODING_AUTO;
525 CloseHandle(hFile);
526 return detect_encoding_of_buffer(buffer, dwNumRead);
530 static LPWSTR dialog_print_to_file(HWND hMainWnd)
532 OPENFILENAMEW ofn;
533 static WCHAR file[MAX_PATH] = {'o','u','t','p','u','t','.','p','r','n',0};
534 static const WCHAR defExt[] = {'p','r','n',0};
536 ZeroMemory(&ofn, sizeof(ofn));
538 ofn.lStructSize = sizeof(ofn);
539 ofn.Flags = OFN_PATHMUSTEXIST | OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT;
540 ofn.hwndOwner = hMainWnd;
541 ofn.lpstrFile = file;
542 ofn.nMaxFile = MAX_PATH;
543 ofn.lpstrDefExt = defExt;
545 if(GetSaveFileNameW(&ofn))
546 return file;
547 else
548 return FALSE;
550 static UINT_PTR CALLBACK OfnHookProc(HWND hdlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
552 static HWND hEncCombo;
554 switch (uMsg)
556 case WM_INITDIALOG:
558 ENCODING enc;
559 hEncCombo = GetDlgItem(hdlg, IDC_OFN_ENCCOMBO);
560 for (enc = MIN_ENCODING; enc <= MAX_ENCODING; enc++)
562 WCHAR szEnc[MAX_STRING_LEN];
563 load_encoding_name(enc, szEnc, ARRAY_SIZE(szEnc));
564 SendMessageW(hEncCombo, CB_ADDSTRING, 0, (LPARAM)szEnc);
566 SendMessageW(hEncCombo, CB_SETCURSEL, (WPARAM)Globals.encOfnCombo, 0);
568 break;
570 case WM_COMMAND:
571 if (LOWORD(wParam) == IDC_OFN_ENCCOMBO &&
572 HIWORD(wParam) == CBN_SELCHANGE)
574 int index = SendMessageW(hEncCombo, CB_GETCURSEL, 0, 0);
575 Globals.encOfnCombo = index==CB_ERR ? ENCODING_ANSI : (ENCODING)index;
578 break;
580 case WM_NOTIFY:
581 switch (((OFNOTIFYW*)lParam)->hdr.code)
583 case CDN_SELCHANGE:
584 if (Globals.bOfnIsOpenDialog)
586 /* Check the start of the selected file for a BOM. */
587 ENCODING enc;
588 WCHAR szFileName[MAX_PATH];
589 SendMessageW(GetParent(hdlg), CDM_GETFILEPATH,
590 ARRAY_SIZE(szFileName), (LPARAM)szFileName);
591 enc = detect_encoding_of_file(szFileName);
592 if (enc != ENCODING_AUTO)
594 Globals.encOfnCombo = enc;
595 SendMessageW(hEncCombo, CB_SETCURSEL, (WPARAM)enc, 0);
598 break;
600 default:
601 break;
603 break;
605 default:
606 break;
608 return 0;
611 VOID DIALOG_FileOpen(VOID)
613 OPENFILENAMEW openfilename;
614 WCHAR szPath[MAX_PATH];
615 static const WCHAR szDefaultExt[] = { 't','x','t',0 };
616 static const WCHAR txt_files[] = { '*','.','t','x','t',0 };
618 ZeroMemory(&openfilename, sizeof(openfilename));
620 lstrcpyW(szPath, txt_files);
622 openfilename.lStructSize = sizeof(openfilename);
623 openfilename.hwndOwner = Globals.hMainWnd;
624 openfilename.hInstance = Globals.hInstance;
625 openfilename.lpstrFilter = Globals.szFilter;
626 openfilename.lpstrFile = szPath;
627 openfilename.nMaxFile = ARRAY_SIZE(szPath);
628 openfilename.Flags = OFN_ENABLETEMPLATE | OFN_ENABLEHOOK | OFN_EXPLORER |
629 OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST |
630 OFN_HIDEREADONLY | OFN_ENABLESIZING;
631 openfilename.lpfnHook = OfnHookProc;
632 openfilename.lpTemplateName = MAKEINTRESOURCEW(IDD_OFN_TEMPLATE);
633 openfilename.lpstrDefExt = szDefaultExt;
635 Globals.encOfnCombo = ENCODING_ANSI;
636 Globals.bOfnIsOpenDialog = TRUE;
638 if (GetOpenFileNameW(&openfilename))
639 DoOpenFile(openfilename.lpstrFile, Globals.encOfnCombo);
642 /* Return FALSE to cancel close */
643 BOOL DIALOG_FileSave(VOID)
645 if (Globals.szFileName[0] == '\0')
646 return DIALOG_FileSaveAs();
647 else
649 switch (DoSaveFile(Globals.szFileName, Globals.encFile))
651 case SAVED_OK: return TRUE;
652 case SHOW_SAVEAS_DIALOG: return DIALOG_FileSaveAs();
653 default: return FALSE;
658 BOOL DIALOG_FileSaveAs(VOID)
660 OPENFILENAMEW saveas;
661 WCHAR szPath[MAX_PATH];
662 static const WCHAR szDefaultExt[] = { 't','x','t',0 };
663 static const WCHAR txt_files[] = { '*','.','t','x','t',0 };
665 ZeroMemory(&saveas, sizeof(saveas));
667 lstrcpyW(szPath, txt_files);
669 saveas.lStructSize = sizeof(OPENFILENAMEW);
670 saveas.hwndOwner = Globals.hMainWnd;
671 saveas.hInstance = Globals.hInstance;
672 saveas.lpstrFilter = Globals.szFilter;
673 saveas.lpstrFile = szPath;
674 saveas.nMaxFile = ARRAY_SIZE(szPath);
675 saveas.Flags = OFN_ENABLETEMPLATE | OFN_ENABLEHOOK | OFN_EXPLORER |
676 OFN_PATHMUSTEXIST | OFN_OVERWRITEPROMPT |
677 OFN_HIDEREADONLY | OFN_ENABLESIZING;
678 saveas.lpfnHook = OfnHookProc;
679 saveas.lpTemplateName = MAKEINTRESOURCEW(IDD_OFN_TEMPLATE);
680 saveas.lpstrDefExt = szDefaultExt;
682 /* Preset encoding to what file was opened/saved last with. */
683 Globals.encOfnCombo = Globals.encFile;
684 Globals.bOfnIsOpenDialog = FALSE;
686 retry:
687 if (!GetSaveFileNameW(&saveas))
688 return FALSE;
690 switch (DoSaveFile(szPath, Globals.encOfnCombo))
692 case SAVED_OK:
693 SetFileNameAndEncoding(szPath, Globals.encOfnCombo);
694 UpdateWindowCaption();
695 return TRUE;
697 case SHOW_SAVEAS_DIALOG:
698 goto retry;
700 default:
701 return FALSE;
705 typedef struct {
706 LPWSTR mptr;
707 LPWSTR mend;
708 LPWSTR lptr;
709 DWORD len;
710 } TEXTINFO, *LPTEXTINFO;
712 static int notepad_print_header(HDC hdc, RECT *rc, BOOL dopage, BOOL header, int page, LPWSTR text)
714 SIZE szMetric;
716 if (*text)
718 /* Write the header or footer */
719 GetTextExtentPoint32W(hdc, text, lstrlenW(text), &szMetric);
720 if (dopage)
721 ExtTextOutW(hdc, (rc->left + rc->right - szMetric.cx) / 2,
722 header ? rc->top : rc->bottom - szMetric.cy,
723 ETO_CLIPPED, rc, text, lstrlenW(text), NULL);
724 return 1;
726 return 0;
729 static WCHAR *expand_header_vars(WCHAR *pattern, int page)
731 int length = 0;
732 int i;
733 BOOL inside = FALSE;
734 WCHAR *buffer = NULL;
736 for (i = 0; pattern[i]; i++)
738 if (inside)
740 if (pattern[i] == '&')
741 length++;
742 else if (pattern[i] == 'p')
743 length += 11;
744 inside = FALSE;
746 else if (pattern[i] == '&')
747 inside = TRUE;
748 else
749 length++;
752 buffer = HeapAlloc(GetProcessHeap(), 0, (length + 1) * sizeof(WCHAR));
753 if (buffer)
755 int j = 0;
756 inside = FALSE;
757 for (i = 0; pattern[i]; i++)
759 if (inside)
761 if (pattern[i] == '&')
762 buffer[j++] = '&';
763 else if (pattern[i] == 'p')
765 static const WCHAR percent_dW[] = {'%','d',0};
766 j += wnsprintfW(&buffer[j], 11, percent_dW, page);
768 inside = FALSE;
770 else if (pattern[i] == '&')
771 inside = TRUE;
772 else
773 buffer[j++] = pattern[i];
775 buffer[j++] = 0;
777 return buffer;
780 static BOOL notepad_print_page(HDC hdc, RECT *rc, BOOL dopage, int page, LPTEXTINFO tInfo)
782 int b, y;
783 TEXTMETRICW tm;
784 SIZE szMetrics;
785 WCHAR *footer_text = NULL;
787 footer_text = expand_header_vars(Globals.szFooter, page);
788 if (footer_text == NULL)
789 return FALSE;
791 if (dopage)
793 if (StartPage(hdc) <= 0)
795 static const WCHAR failedW[] = { 'S','t','a','r','t','P','a','g','e',' ','f','a','i','l','e','d',0 };
796 static const WCHAR errorW[] = { 'P','r','i','n','t',' ','E','r','r','o','r',0 };
797 MessageBoxW(Globals.hMainWnd, failedW, errorW, MB_ICONEXCLAMATION);
798 HeapFree(GetProcessHeap(), 0, footer_text);
799 return FALSE;
803 GetTextMetricsW(hdc, &tm);
804 y = rc->top + notepad_print_header(hdc, rc, dopage, TRUE, page, Globals.szFileName) * tm.tmHeight;
805 b = rc->bottom - 2 * notepad_print_header(hdc, rc, FALSE, FALSE, page, footer_text) * tm.tmHeight;
807 do {
808 INT m, n;
810 if (!tInfo->len)
812 /* find the end of the line */
813 while (tInfo->mptr < tInfo->mend && *tInfo->mptr != '\n' && *tInfo->mptr != '\r')
815 if (*tInfo->mptr == '\t')
817 /* replace tabs with spaces */
818 for (m = 0; m < SPACES_IN_TAB; m++)
820 if (tInfo->len < PRINT_LEN_MAX)
821 tInfo->lptr[tInfo->len++] = ' ';
822 else if (Globals.bWrapLongLines)
823 break;
826 else if (tInfo->len < PRINT_LEN_MAX)
827 tInfo->lptr[tInfo->len++] = *tInfo->mptr;
829 if (tInfo->len >= PRINT_LEN_MAX && Globals.bWrapLongLines)
830 break;
832 tInfo->mptr++;
836 /* Find out how much we should print if line wrapping is enabled */
837 if (Globals.bWrapLongLines)
839 GetTextExtentExPointW(hdc, tInfo->lptr, tInfo->len, rc->right - rc->left, &n, NULL, &szMetrics);
840 if (n < tInfo->len && tInfo->lptr[n] != ' ')
842 m = n;
843 /* Don't wrap words unless it's a single word over the entire line */
844 while (m && tInfo->lptr[m] != ' ') m--;
845 if (m > 0) n = m + 1;
848 else
849 n = tInfo->len;
851 if (dopage)
852 ExtTextOutW(hdc, rc->left, y, ETO_CLIPPED, rc, tInfo->lptr, n, NULL);
854 tInfo->len -= n;
856 if (tInfo->len)
858 memcpy(tInfo->lptr, tInfo->lptr + n, tInfo->len * sizeof(WCHAR));
859 y += tm.tmHeight + tm.tmExternalLeading;
861 else
863 /* find the next line */
864 while (tInfo->mptr < tInfo->mend && y < b && (*tInfo->mptr == '\n' || *tInfo->mptr == '\r'))
866 if (*tInfo->mptr == '\n')
867 y += tm.tmHeight + tm.tmExternalLeading;
868 tInfo->mptr++;
871 } while (tInfo->mptr < tInfo->mend && y < b);
873 notepad_print_header(hdc, rc, dopage, FALSE, page, footer_text);
874 if (dopage)
876 EndPage(hdc);
878 HeapFree(GetProcessHeap(), 0, footer_text);
879 return TRUE;
882 VOID DIALOG_FilePrint(VOID)
884 DOCINFOW di;
885 PRINTDLGW printer;
886 int page, dopage, copy;
887 LOGFONTW lfFont;
888 HFONT hTextFont, old_font = 0;
889 DWORD size;
890 BOOL ret = FALSE;
891 RECT rc;
892 LPWSTR pTemp;
893 TEXTINFO tInfo;
894 WCHAR cTemp[PRINT_LEN_MAX];
896 /* Get Current Settings */
897 ZeroMemory(&printer, sizeof(printer));
898 printer.lStructSize = sizeof(printer);
899 printer.hwndOwner = Globals.hMainWnd;
900 printer.hDevMode = Globals.hDevMode;
901 printer.hDevNames = Globals.hDevNames;
902 printer.hInstance = Globals.hInstance;
904 /* Set some default flags */
905 printer.Flags = PD_RETURNDC | PD_NOSELECTION;
906 printer.nFromPage = 0;
907 printer.nMinPage = 1;
908 /* we really need to calculate number of pages to set nMaxPage and nToPage */
909 printer.nToPage = 0;
910 printer.nMaxPage = -1;
911 /* Let commdlg manage copy settings */
912 printer.nCopies = (WORD)PD_USEDEVMODECOPIES;
914 if (!PrintDlgW(&printer)) return;
916 Globals.hDevMode = printer.hDevMode;
917 Globals.hDevNames = printer.hDevNames;
919 SetMapMode(printer.hDC, MM_TEXT);
921 /* initialize DOCINFO */
922 di.cbSize = sizeof(DOCINFOW);
923 di.lpszDocName = Globals.szFileTitle;
924 di.lpszOutput = NULL;
925 di.lpszDatatype = NULL;
926 di.fwType = 0;
928 if(printer.Flags & PD_PRINTTOFILE)
930 di.lpszOutput = dialog_print_to_file(printer.hwndOwner);
931 if(!di.lpszOutput)
932 return;
935 /* Get the file text */
936 size = GetWindowTextLengthW(Globals.hEdit) + 1;
937 pTemp = HeapAlloc(GetProcessHeap(), 0, size * sizeof(WCHAR));
938 if (!pTemp)
940 DeleteDC(printer.hDC);
941 ShowLastError();
942 return;
944 size = GetWindowTextW(Globals.hEdit, pTemp, size);
946 if (StartDocW(printer.hDC, &di) > 0)
948 /* Get the page margins in pixels. */
949 rc.top = MulDiv(Globals.iMarginTop, GetDeviceCaps(printer.hDC, LOGPIXELSY), 2540) -
950 GetDeviceCaps(printer.hDC, PHYSICALOFFSETY);
951 rc.bottom = GetDeviceCaps(printer.hDC, PHYSICALHEIGHT) -
952 MulDiv(Globals.iMarginBottom, GetDeviceCaps(printer.hDC, LOGPIXELSY), 2540);
953 rc.left = MulDiv(Globals.iMarginLeft, GetDeviceCaps(printer.hDC, LOGPIXELSX), 2540) -
954 GetDeviceCaps(printer.hDC, PHYSICALOFFSETX);
955 rc.right = GetDeviceCaps(printer.hDC, PHYSICALWIDTH) -
956 MulDiv(Globals.iMarginRight, GetDeviceCaps(printer.hDC, LOGPIXELSX), 2540);
958 /* Create a font for the printer resolution */
959 lfFont = Globals.lfFont;
960 lfFont.lfHeight = MulDiv(lfFont.lfHeight, GetDeviceCaps(printer.hDC, LOGPIXELSY), get_dpi());
961 /* Make the font a bit lighter */
962 lfFont.lfWeight -= 100;
963 hTextFont = CreateFontIndirectW(&lfFont);
964 old_font = SelectObject(printer.hDC, hTextFont);
966 for (copy = 1; copy <= printer.nCopies; copy++)
968 page = 1;
970 tInfo.mptr = pTemp;
971 tInfo.mend = pTemp + size;
972 tInfo.lptr = cTemp;
973 tInfo.len = 0;
975 do {
976 if (printer.Flags & PD_PAGENUMS)
978 /* a specific range of pages is selected, so
979 * skip pages that are not to be printed
981 if (page > printer.nToPage)
982 break;
983 else if (page >= printer.nFromPage)
984 dopage = 1;
985 else
986 dopage = 0;
988 else
989 dopage = 1;
991 ret = notepad_print_page(printer.hDC, &rc, dopage, page, &tInfo);
992 page++;
993 } while (ret && tInfo.mptr < tInfo.mend);
995 if (!ret) break;
997 EndDoc(printer.hDC);
998 SelectObject(printer.hDC, old_font);
999 DeleteObject(hTextFont);
1001 DeleteDC(printer.hDC);
1002 HeapFree(GetProcessHeap(), 0, pTemp);
1005 VOID DIALOG_FilePrinterSetup(VOID)
1007 PRINTDLGW printer;
1009 ZeroMemory(&printer, sizeof(printer));
1010 printer.lStructSize = sizeof(printer);
1011 printer.hwndOwner = Globals.hMainWnd;
1012 printer.hDevMode = Globals.hDevMode;
1013 printer.hDevNames = Globals.hDevNames;
1014 printer.hInstance = Globals.hInstance;
1015 printer.Flags = PD_PRINTSETUP;
1016 printer.nCopies = 1;
1018 PrintDlgW(&printer);
1020 Globals.hDevMode = printer.hDevMode;
1021 Globals.hDevNames = printer.hDevNames;
1024 VOID DIALOG_FileExit(VOID)
1026 PostMessageW(Globals.hMainWnd, WM_CLOSE, 0, 0l);
1029 VOID DIALOG_EditUndo(VOID)
1031 SendMessageW(Globals.hEdit, EM_UNDO, 0, 0);
1034 VOID DIALOG_EditCut(VOID)
1036 SendMessageW(Globals.hEdit, WM_CUT, 0, 0);
1039 VOID DIALOG_EditCopy(VOID)
1041 SendMessageW(Globals.hEdit, WM_COPY, 0, 0);
1044 VOID DIALOG_EditPaste(VOID)
1046 SendMessageW(Globals.hEdit, WM_PASTE, 0, 0);
1049 VOID DIALOG_EditDelete(VOID)
1051 SendMessageW(Globals.hEdit, WM_CLEAR, 0, 0);
1054 VOID DIALOG_EditSelectAll(VOID)
1056 SendMessageW(Globals.hEdit, EM_SETSEL, 0, -1);
1059 VOID DIALOG_EditTimeDate(VOID)
1061 SYSTEMTIME st;
1062 WCHAR szDate[MAX_STRING_LEN];
1063 static const WCHAR spaceW[] = { ' ',0 };
1065 GetLocalTime(&st);
1067 GetTimeFormatW(LOCALE_USER_DEFAULT, TIME_NOSECONDS, &st, NULL, szDate, MAX_STRING_LEN);
1068 SendMessageW(Globals.hEdit, EM_REPLACESEL, TRUE, (LPARAM)szDate);
1070 SendMessageW(Globals.hEdit, EM_REPLACESEL, TRUE, (LPARAM)spaceW);
1072 GetDateFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL, szDate, MAX_STRING_LEN);
1073 SendMessageW(Globals.hEdit, EM_REPLACESEL, TRUE, (LPARAM)szDate);
1076 VOID DIALOG_EditWrap(VOID)
1078 BOOL modify = FALSE;
1079 static const WCHAR editW[] = { 'e','d','i','t',0 };
1080 DWORD dwStyle = WS_CHILD | WS_VISIBLE | WS_BORDER | WS_VSCROLL |
1081 ES_AUTOVSCROLL | ES_MULTILINE;
1082 RECT rc;
1083 DWORD size;
1084 LPWSTR pTemp;
1086 size = GetWindowTextLengthW(Globals.hEdit) + 1;
1087 pTemp = HeapAlloc(GetProcessHeap(), 0, size * sizeof(WCHAR));
1088 if (!pTemp)
1090 ShowLastError();
1091 return;
1093 GetWindowTextW(Globals.hEdit, pTemp, size);
1094 modify = SendMessageW(Globals.hEdit, EM_GETMODIFY, 0, 0);
1095 DestroyWindow(Globals.hEdit);
1096 GetClientRect(Globals.hMainWnd, &rc);
1097 if( Globals.bWrapLongLines ) dwStyle |= WS_HSCROLL | ES_AUTOHSCROLL;
1098 Globals.hEdit = CreateWindowExW(WS_EX_CLIENTEDGE, editW, NULL, dwStyle,
1099 0, 0, rc.right, rc.bottom, Globals.hMainWnd,
1100 NULL, Globals.hInstance, NULL);
1101 SendMessageW(Globals.hEdit, WM_SETFONT, (WPARAM)Globals.hFont, FALSE);
1102 SetWindowTextW(Globals.hEdit, pTemp);
1103 SendMessageW(Globals.hEdit, EM_SETMODIFY, modify, 0);
1104 SetFocus(Globals.hEdit);
1105 HeapFree(GetProcessHeap(), 0, pTemp);
1107 Globals.bWrapLongLines = !Globals.bWrapLongLines;
1108 CheckMenuItem(GetMenu(Globals.hMainWnd), CMD_WRAP,
1109 MF_BYCOMMAND | (Globals.bWrapLongLines ? MF_CHECKED : MF_UNCHECKED));
1112 VOID DIALOG_SelectFont(VOID)
1114 CHOOSEFONTW cf;
1115 LOGFONTW lf=Globals.lfFont;
1117 ZeroMemory( &cf, sizeof(cf) );
1118 cf.lStructSize=sizeof(cf);
1119 cf.hwndOwner=Globals.hMainWnd;
1120 cf.lpLogFont=&lf;
1121 cf.Flags=CF_SCREENFONTS | CF_INITTOLOGFONTSTRUCT | CF_NOVERTFONTS;
1123 if( ChooseFontW(&cf) )
1125 HFONT currfont=Globals.hFont;
1127 Globals.hFont=CreateFontIndirectW( &lf );
1128 Globals.lfFont=lf;
1129 SendMessageW( Globals.hEdit, WM_SETFONT, (WPARAM)Globals.hFont, TRUE );
1130 if( currfont!=NULL )
1131 DeleteObject( currfont );
1135 VOID DIALOG_Search(VOID)
1137 /* Allow only one search/replace dialog to open */
1138 if(Globals.hFindReplaceDlg != NULL)
1140 SetActiveWindow(Globals.hFindReplaceDlg);
1141 return;
1144 ZeroMemory(&Globals.find, sizeof(Globals.find));
1145 Globals.find.lStructSize = sizeof(Globals.find);
1146 Globals.find.hwndOwner = Globals.hMainWnd;
1147 Globals.find.hInstance = Globals.hInstance;
1148 Globals.find.lpstrFindWhat = Globals.szFindText;
1149 Globals.find.wFindWhatLen = ARRAY_SIZE(Globals.szFindText);
1150 Globals.find.Flags = FR_DOWN|FR_HIDEWHOLEWORD;
1152 /* We only need to create the modal FindReplace dialog which will */
1153 /* notify us of incoming events using hMainWnd Window Messages */
1155 Globals.hFindReplaceDlg = FindTextW(&Globals.find);
1156 assert(Globals.hFindReplaceDlg !=0);
1159 VOID DIALOG_SearchNext(VOID)
1161 if (Globals.lastFind.lpstrFindWhat == NULL)
1162 DIALOG_Search();
1163 else /* use the last find data */
1164 NOTEPAD_DoFind(&Globals.lastFind);
1167 VOID DIALOG_Replace(VOID)
1169 /* Allow only one search/replace dialog to open */
1170 if(Globals.hFindReplaceDlg != NULL)
1172 SetActiveWindow(Globals.hFindReplaceDlg);
1173 return;
1176 ZeroMemory(&Globals.find, sizeof(Globals.find));
1177 Globals.find.lStructSize = sizeof(Globals.find);
1178 Globals.find.hwndOwner = Globals.hMainWnd;
1179 Globals.find.hInstance = Globals.hInstance;
1180 Globals.find.lpstrFindWhat = Globals.szFindText;
1181 Globals.find.wFindWhatLen = ARRAY_SIZE(Globals.szFindText);
1182 Globals.find.lpstrReplaceWith = Globals.szReplaceText;
1183 Globals.find.wReplaceWithLen = ARRAY_SIZE(Globals.szReplaceText);
1184 Globals.find.Flags = FR_DOWN|FR_HIDEWHOLEWORD;
1186 /* We only need to create the modal FindReplace dialog which will */
1187 /* notify us of incoming events using hMainWnd Window Messages */
1189 Globals.hFindReplaceDlg = ReplaceTextW(&Globals.find);
1190 assert(Globals.hFindReplaceDlg !=0);
1193 VOID DIALOG_HelpContents(VOID)
1195 WinHelpW(Globals.hMainWnd, helpfileW, HELP_INDEX, 0);
1198 VOID DIALOG_HelpAboutNotepad(VOID)
1200 static const WCHAR notepadW[] = { 'W','i','n','e',' ','N','o','t','e','p','a','d',0 };
1201 WCHAR szNotepad[MAX_STRING_LEN];
1202 HICON icon = LoadImageW(Globals.hInstance, MAKEINTRESOURCEW(IDI_NOTEPAD),
1203 IMAGE_ICON, 48, 48, LR_SHARED);
1205 LoadStringW(Globals.hInstance, STRING_NOTEPAD, szNotepad, ARRAY_SIZE(szNotepad));
1206 ShellAboutW(Globals.hMainWnd, szNotepad, notepadW, icon);
1210 /***********************************************************************
1212 * DIALOG_FilePageSetup
1214 VOID DIALOG_FilePageSetup(void)
1216 DialogBoxW(Globals.hInstance, MAKEINTRESOURCEW(DIALOG_PAGESETUP),
1217 Globals.hMainWnd, DIALOG_PAGESETUP_DlgProc);
1221 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1223 * DIALOG_PAGESETUP_DlgProc
1226 static INT_PTR WINAPI DIALOG_PAGESETUP_DlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
1229 switch (msg)
1231 case WM_COMMAND:
1232 switch (wParam)
1234 case IDOK:
1235 /* save user input and close dialog */
1236 GetDlgItemTextW(hDlg, IDC_PAGESETUP_HEADERVALUE, Globals.szHeader, ARRAY_SIZE(Globals.szHeader));
1237 GetDlgItemTextW(hDlg, IDC_PAGESETUP_FOOTERVALUE, Globals.szFooter, ARRAY_SIZE(Globals.szFooter));
1239 Globals.iMarginTop = GetDlgItemInt(hDlg, IDC_PAGESETUP_TOPVALUE, NULL, FALSE) * 100;
1240 Globals.iMarginBottom = GetDlgItemInt(hDlg, IDC_PAGESETUP_BOTTOMVALUE, NULL, FALSE) * 100;
1241 Globals.iMarginLeft = GetDlgItemInt(hDlg, IDC_PAGESETUP_LEFTVALUE, NULL, FALSE) * 100;
1242 Globals.iMarginRight = GetDlgItemInt(hDlg, IDC_PAGESETUP_RIGHTVALUE, NULL, FALSE) * 100;
1243 EndDialog(hDlg, IDOK);
1244 return TRUE;
1246 case IDCANCEL:
1247 /* discard user input and close dialog */
1248 EndDialog(hDlg, IDCANCEL);
1249 return TRUE;
1251 case IDHELP:
1253 /* FIXME: Bring this to work */
1254 static const WCHAR sorryW[] = { 'S','o','r','r','y',',',' ','n','o',' ','h','e','l','p',' ','a','v','a','i','l','a','b','l','e',0 };
1255 static const WCHAR helpW[] = { 'H','e','l','p',0 };
1256 MessageBoxW(Globals.hMainWnd, sorryW, helpW, MB_ICONEXCLAMATION);
1257 return TRUE;
1260 default:
1261 break;
1263 break;
1265 case WM_INITDIALOG:
1266 /* fetch last user input prior to display dialog */
1267 SetDlgItemTextW(hDlg, IDC_PAGESETUP_HEADERVALUE, Globals.szHeader);
1268 SetDlgItemTextW(hDlg, IDC_PAGESETUP_FOOTERVALUE, Globals.szFooter);
1269 SetDlgItemInt(hDlg, IDC_PAGESETUP_TOPVALUE, Globals.iMarginTop / 100, FALSE);
1270 SetDlgItemInt(hDlg, IDC_PAGESETUP_BOTTOMVALUE, Globals.iMarginBottom / 100, FALSE);
1271 SetDlgItemInt(hDlg, IDC_PAGESETUP_LEFTVALUE, Globals.iMarginLeft / 100, FALSE);
1272 SetDlgItemInt(hDlg, IDC_PAGESETUP_RIGHTVALUE, Globals.iMarginRight / 100, FALSE);
1273 break;
1276 return FALSE;