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
37 #define SPACES_IN_TAB 8
38 #define PRINT_LEN_MAX 500
40 static const WCHAR helpfileW
[] = { 'n','o','t','e','p','a','d','.','h','l','p',0 };
42 static INT_PTR WINAPI
DIALOG_PAGESETUP_DlgProc(HWND hDlg
, UINT msg
, WPARAM wParam
, LPARAM lParam
);
44 /* Swap bytes of WCHAR buffer (big-endian <-> little-endian). */
45 static inline void byteswap_wide_string(LPWSTR str
, UINT num
)
48 for (i
= 0; i
< num
; i
++) str
[i
] = RtlUshortByteSwap(str
[i
]);
51 static void load_encoding_name(ENCODING enc
, WCHAR
* buffer
, int length
)
55 case ENCODING_UTF16LE
:
56 LoadStringW(Globals
.hInstance
, STRING_UNICODE_LE
, buffer
, length
);
59 case ENCODING_UTF16BE
:
60 LoadStringW(Globals
.hInstance
, STRING_UNICODE_BE
, buffer
, length
);
64 LoadStringW(Globals
.hInstance
, STRING_UTF8
, buffer
, length
);
70 GetCPInfoExW(CP_ACP
, 0, &cpi
);
71 lstrcpynW(buffer
, cpi
.CodePageName
, length
);
76 assert(0 && "bad encoding in load_encoding_name");
81 VOID
ShowLastError(void)
83 DWORD error
= GetLastError();
84 if (error
!= NO_ERROR
)
87 WCHAR szTitle
[MAX_STRING_LEN
];
89 LoadStringW(Globals
.hInstance
, STRING_ERROR
, szTitle
, ARRAY_SIZE(szTitle
));
91 FORMAT_MESSAGE_ALLOCATE_BUFFER
| FORMAT_MESSAGE_FROM_SYSTEM
,
92 NULL
, error
, 0, (LPWSTR
)&lpMsgBuf
, 0, NULL
);
93 MessageBoxW(NULL
, lpMsgBuf
, szTitle
, MB_OK
| MB_ICONERROR
);
99 * Sets the caption of the main window according to Globals.szFileTitle:
100 * Untitled - Notepad if no file is open
101 * filename - Notepad if a file is given
103 void UpdateWindowCaption(void)
105 static const WCHAR hyphenW
[] = { ' ','-',' ',0 };
107 WCHAR szCaption
[ARRAY_SIZE(Globals
.szFileTitle
) + ARRAY_SIZE(hyphenW
) + ARRAY_SIZE(szNotepad
)];
109 if (Globals
.szFileTitle
[0] != '\0')
110 lstrcpyW(szCaption
, Globals
.szFileTitle
);
112 LoadStringW(Globals
.hInstance
, STRING_UNTITLED
, szCaption
, ARRAY_SIZE(szCaption
));
114 LoadStringW(Globals
.hInstance
, STRING_NOTEPAD
, szNotepad
, ARRAY_SIZE(szNotepad
));
115 lstrcatW(szCaption
, hyphenW
);
116 lstrcatW(szCaption
, szNotepad
);
118 SetWindowTextW(Globals
.hMainWnd
, szCaption
);
121 int DIALOG_StringMsgBox(HWND hParent
, int formatId
, LPCWSTR szString
, DWORD dwFlags
)
123 WCHAR szMessage
[MAX_STRING_LEN
];
124 WCHAR szResource
[MAX_STRING_LEN
];
126 /* Load and format szMessage */
127 LoadStringW(Globals
.hInstance
, formatId
, szResource
, ARRAY_SIZE(szResource
));
128 wnsprintfW(szMessage
, ARRAY_SIZE(szMessage
), szResource
, szString
);
131 if ((dwFlags
& MB_ICONMASK
) == MB_ICONEXCLAMATION
)
132 LoadStringW(Globals
.hInstance
, STRING_ERROR
, szResource
, ARRAY_SIZE(szResource
));
134 LoadStringW(Globals
.hInstance
, STRING_NOTEPAD
, szResource
, ARRAY_SIZE(szResource
));
136 /* Display Modal Dialog */
138 hParent
= Globals
.hMainWnd
;
139 return MessageBoxW(hParent
, szMessage
, szResource
, dwFlags
);
142 static void AlertFileNotFound(LPCWSTR szFileName
)
144 DIALOG_StringMsgBox(NULL
, STRING_NOTFOUND
, szFileName
, MB_ICONEXCLAMATION
|MB_OK
);
147 static int AlertFileNotSaved(LPCWSTR szFileName
)
149 WCHAR szUntitled
[MAX_STRING_LEN
];
151 LoadStringW(Globals
.hInstance
, STRING_UNTITLED
, szUntitled
, ARRAY_SIZE(szUntitled
));
152 return DIALOG_StringMsgBox(NULL
, STRING_NOTSAVED
, szFileName
[0] ? szFileName
: szUntitled
,
153 MB_ICONQUESTION
|MB_YESNOCANCEL
);
156 static int AlertUnicodeCharactersLost(LPCWSTR szFileName
)
158 WCHAR szCaption
[MAX_STRING_LEN
];
159 WCHAR szMsgFormat
[MAX_STRING_LEN
];
160 WCHAR szEnc
[MAX_STRING_LEN
];
165 LoadStringW(Globals
.hInstance
, STRING_NOTEPAD
, szCaption
,
166 ARRAY_SIZE(szCaption
));
167 LoadStringW(Globals
.hInstance
, STRING_LOSS_OF_UNICODE_CHARACTERS
,
168 szMsgFormat
, ARRAY_SIZE(szMsgFormat
));
169 load_encoding_name(ENCODING_ANSI
, szEnc
, ARRAY_SIZE(szEnc
));
170 args
[0] = (DWORD_PTR
)szFileName
;
171 args
[1] = (DWORD_PTR
)szEnc
;
172 FormatMessageW(FORMAT_MESSAGE_FROM_STRING
|FORMAT_MESSAGE_ALLOCATE_BUFFER
|FORMAT_MESSAGE_ARGUMENT_ARRAY
, szMsgFormat
, 0, 0, (LPWSTR
)&szMsg
, 0, (va_list *)args
);
173 rc
= MessageBoxW(Globals
.hMainWnd
, szMsg
, szCaption
,
174 MB_OKCANCEL
|MB_ICONEXCLAMATION
);
181 * TRUE - if file exists
182 * FALSE - if file does not exist
184 BOOL
FileExists(LPCWSTR szFilename
)
186 WIN32_FIND_DATAW entry
;
189 hFile
= FindFirstFileW(szFilename
, &entry
);
192 return (hFile
!= INVALID_HANDLE_VALUE
);
195 static inline BOOL
is_conversion_to_ansi_lossy(LPCWSTR textW
, int lenW
)
198 WideCharToMultiByte(CP_ACP
, WC_NO_BEST_FIT_CHARS
, textW
, lenW
, NULL
, 0,
210 /* szFileName is the filename to save under; enc is the encoding to use.
212 * If the function succeeds, it returns SAVED_OK.
213 * If the function fails, it returns SAVE_FAILED.
214 * If Unicode data could be lost due to conversion to a non-Unicode character
215 * set, a warning is displayed. The user can continue (and the function carries
216 * on), or cancel (and the function returns SHOW_SAVEAS_DIALOG).
218 static SAVE_STATUS
DoSaveFile(LPCWSTR szFileName
, ENCODING enc
)
227 /* lenW includes the byte-order mark, but not the \0. */
228 lenW
= GetWindowTextLengthW(Globals
.hEdit
) + 1;
229 textW
= HeapAlloc(GetProcessHeap(), 0, (lenW
+1) * sizeof(WCHAR
));
235 textW
[0] = (WCHAR
) 0xfeff;
236 lenW
= GetWindowTextW(Globals
.hEdit
, textW
+1, lenW
) + 1;
240 case ENCODING_UTF16BE
:
241 byteswap_wide_string(textW
, lenW
);
244 case ENCODING_UTF16LE
:
245 size
= lenW
* sizeof(WCHAR
);
250 size
= WideCharToMultiByte(CP_UTF8
, 0, textW
, lenW
, NULL
, 0, NULL
, NULL
);
251 pBytes
= HeapAlloc(GetProcessHeap(), 0, size
);
255 HeapFree(GetProcessHeap(), 0, textW
);
258 WideCharToMultiByte(CP_UTF8
, 0, textW
, lenW
, pBytes
, size
, NULL
, NULL
);
259 HeapFree(GetProcessHeap(), 0, textW
);
263 if (is_conversion_to_ansi_lossy(textW
+1, lenW
-1)
264 && AlertUnicodeCharactersLost(szFileName
) == IDCANCEL
)
266 HeapFree(GetProcessHeap(), 0, textW
);
267 return SHOW_SAVEAS_DIALOG
;
270 size
= WideCharToMultiByte(CP_ACP
, 0, textW
+1, lenW
-1, NULL
, 0, NULL
, NULL
);
271 pBytes
= HeapAlloc(GetProcessHeap(), 0, size
);
275 HeapFree(GetProcessHeap(), 0, textW
);
278 WideCharToMultiByte(CP_ACP
, 0, textW
+1, lenW
-1, pBytes
, size
, NULL
, NULL
);
279 HeapFree(GetProcessHeap(), 0, textW
);
283 hFile
= CreateFileW(szFileName
, GENERIC_WRITE
, FILE_SHARE_WRITE
,
284 NULL
, OPEN_ALWAYS
, FILE_ATTRIBUTE_NORMAL
, NULL
);
285 if(hFile
== INVALID_HANDLE_VALUE
)
288 HeapFree(GetProcessHeap(), 0, pBytes
);
291 if (!WriteFile(hFile
, pBytes
, size
, &dwNumWrite
, NULL
))
295 HeapFree(GetProcessHeap(), 0, pBytes
);
300 HeapFree(GetProcessHeap(), 0, pBytes
);
302 SendMessageW(Globals
.hEdit
, EM_SETMODIFY
, FALSE
, 0);
308 * TRUE - User agreed to close (both save/don't save)
309 * FALSE - User cancelled close by selecting "Cancel"
311 BOOL
DoCloseFile(void)
314 static const WCHAR empty_strW
[] = { 0 };
316 nResult
=GetWindowTextLengthW(Globals
.hEdit
);
317 if (SendMessageW(Globals
.hEdit
, EM_GETMODIFY
, 0, 0) &&
318 (nResult
|| Globals
.szFileName
[0]))
320 /* prompt user to save changes */
321 nResult
= AlertFileNotSaved(Globals
.szFileName
);
323 case IDYES
: return DIALOG_FileSave();
327 case IDCANCEL
: return(FALSE
);
329 default: return(FALSE
);
333 SetFileNameAndEncoding(empty_strW
, ENCODING_ANSI
);
335 UpdateWindowCaption();
339 static inline ENCODING
detect_encoding_of_buffer(const void* buffer
, int size
)
341 static const char bom_utf8
[] = { 0xef, 0xbb, 0xbf };
342 if (size
>= sizeof(bom_utf8
) && !memcmp(buffer
, bom_utf8
, sizeof(bom_utf8
)))
343 return ENCODING_UTF8
;
346 int flags
= IS_TEXT_UNICODE_SIGNATURE
|
347 IS_TEXT_UNICODE_REVERSE_SIGNATURE
|
348 IS_TEXT_UNICODE_ODD_LENGTH
;
349 IsTextUnicode(buffer
, size
, &flags
);
350 if (flags
& IS_TEXT_UNICODE_SIGNATURE
)
351 return ENCODING_UTF16LE
;
352 else if (flags
& IS_TEXT_UNICODE_REVERSE_SIGNATURE
)
353 return ENCODING_UTF16BE
;
355 return ENCODING_ANSI
;
359 void DoOpenFile(LPCWSTR szFileName
, ENCODING enc
)
361 static const WCHAR dotlog
[] = { '.','L','O','G',0 };
371 /* Close any files and prompt to save changes */
375 hFile
= CreateFileW(szFileName
, GENERIC_READ
, FILE_SHARE_READ
| FILE_SHARE_WRITE
, NULL
,
376 OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
, NULL
);
377 if(hFile
== INVALID_HANDLE_VALUE
)
379 AlertFileNotFound(szFileName
);
383 size
= GetFileSize(hFile
, NULL
);
384 if (size
== INVALID_FILE_SIZE
)
391 /* Extra memory for (WCHAR)'\0'-termination. */
392 pTemp
= HeapAlloc(GetProcessHeap(), 0, size
+2);
400 if (!ReadFile(hFile
, pTemp
, size
, &dwNumRead
, NULL
))
403 HeapFree(GetProcessHeap(), 0, pTemp
);
412 if (enc
== ENCODING_AUTO
)
413 enc
= detect_encoding_of_buffer(pTemp
, size
);
414 else if (size
>= 2 && (enc
==ENCODING_UTF16LE
|| enc
==ENCODING_UTF16BE
))
416 /* If UTF-16 (BE or LE) is selected, and there is a UTF-16 BOM,
417 * override the selection (like native Notepad).
419 if ((BYTE
)pTemp
[0] == 0xff && (BYTE
)pTemp
[1] == 0xfe)
420 enc
= ENCODING_UTF16LE
;
421 else if ((BYTE
)pTemp
[0] == 0xfe && (BYTE
)pTemp
[1] == 0xff)
422 enc
= ENCODING_UTF16BE
;
427 case ENCODING_UTF16BE
:
428 byteswap_wide_string((WCHAR
*) pTemp
, size
/sizeof(WCHAR
));
429 /* Forget whether the file is BE or LE, like native Notepad. */
430 enc
= ENCODING_UTF16LE
;
434 case ENCODING_UTF16LE
:
435 textW
= (LPWSTR
)pTemp
;
436 lenW
= size
/sizeof(WCHAR
);
441 int cp
= (enc
==ENCODING_UTF8
) ? CP_UTF8
: CP_ACP
;
442 lenW
= MultiByteToWideChar(cp
, 0, pTemp
, size
, NULL
, 0);
443 textW
= HeapAlloc(GetProcessHeap(), 0, (lenW
+1) * sizeof(WCHAR
));
447 HeapFree(GetProcessHeap(), 0, pTemp
);
450 MultiByteToWideChar(cp
, 0, pTemp
, size
, textW
, lenW
);
451 HeapFree(GetProcessHeap(), 0, pTemp
);
456 /* Replace '\0's with spaces. Other than creating a custom control that
457 * can deal with '\0' characters, it's the best that can be done.
459 for (i
= 0; i
< lenW
; i
++)
460 if (textW
[i
] == '\0')
464 if (lenW
>= 1 && textW
[0] == 0xfeff)
465 SetWindowTextW(Globals
.hEdit
, textW
+1);
467 SetWindowTextW(Globals
.hEdit
, textW
);
469 HeapFree(GetProcessHeap(), 0, textW
);
471 SendMessageW(Globals
.hEdit
, EM_SETMODIFY
, FALSE
, 0);
472 SendMessageW(Globals
.hEdit
, EM_EMPTYUNDOBUFFER
, 0, 0);
473 SetFocus(Globals
.hEdit
);
475 /* If the file starts with .LOG, add a time/date at the end and set cursor after */
476 if (GetWindowTextW(Globals
.hEdit
, log
, ARRAY_SIZE(log
)) && !lstrcmpW(log
, dotlog
))
478 static const WCHAR lfW
[] = { '\r','\n',0 };
479 SendMessageW(Globals
.hEdit
, EM_SETSEL
, GetWindowTextLengthW(Globals
.hEdit
), -1);
480 SendMessageW(Globals
.hEdit
, EM_REPLACESEL
, TRUE
, (LPARAM
)lfW
);
481 DIALOG_EditTimeDate();
482 SendMessageW(Globals
.hEdit
, EM_REPLACESEL
, TRUE
, (LPARAM
)lfW
);
485 SetFileNameAndEncoding(szFileName
, enc
);
486 UpdateWindowCaption();
489 VOID
DIALOG_FileNew(VOID
)
491 static const WCHAR empty_strW
[] = { 0 };
493 /* Close any files and prompt to save changes */
495 SetWindowTextW(Globals
.hEdit
, empty_strW
);
496 SendMessageW(Globals
.hEdit
, EM_EMPTYUNDOBUFFER
, 0, 0);
497 SetFocus(Globals
.hEdit
);
501 /* Used to detect encoding of files selected in Open dialog.
502 * Returns ENCODING_AUTO if file can't be read, etc.
504 static ENCODING
detect_encoding_of_file(LPCWSTR szFileName
)
507 HANDLE hFile
= CreateFileW(szFileName
, GENERIC_READ
, FILE_SHARE_READ
, NULL
,
508 OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
, NULL
);
509 if (hFile
== INVALID_HANDLE_VALUE
)
510 return ENCODING_AUTO
;
511 size
= GetFileSize(hFile
, NULL
);
512 if (size
== INVALID_FILE_SIZE
)
515 return ENCODING_AUTO
;
520 BYTE buffer
[MAX_STRING_LEN
];
521 if (!ReadFile(hFile
, buffer
, min(size
, sizeof(buffer
)), &dwNumRead
, NULL
))
524 return ENCODING_AUTO
;
527 return detect_encoding_of_buffer(buffer
, dwNumRead
);
531 static LPWSTR
dialog_print_to_file(HWND hMainWnd
)
534 static WCHAR file
[MAX_PATH
] = {'o','u','t','p','u','t','.','p','r','n',0};
535 static const WCHAR defExt
[] = {'p','r','n',0};
537 ZeroMemory(&ofn
, sizeof(ofn
));
539 ofn
.lStructSize
= sizeof(ofn
);
540 ofn
.Flags
= OFN_PATHMUSTEXIST
| OFN_HIDEREADONLY
| OFN_OVERWRITEPROMPT
;
541 ofn
.hwndOwner
= hMainWnd
;
542 ofn
.lpstrFile
= file
;
543 ofn
.nMaxFile
= MAX_PATH
;
544 ofn
.lpstrDefExt
= defExt
;
546 if(GetSaveFileNameW(&ofn
))
551 static UINT_PTR CALLBACK
OfnHookProc(HWND hdlg
, UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
553 static HWND hEncCombo
;
560 hEncCombo
= GetDlgItem(hdlg
, IDC_OFN_ENCCOMBO
);
561 for (enc
= MIN_ENCODING
; enc
<= MAX_ENCODING
; enc
++)
563 WCHAR szEnc
[MAX_STRING_LEN
];
564 load_encoding_name(enc
, szEnc
, ARRAY_SIZE(szEnc
));
565 SendMessageW(hEncCombo
, CB_ADDSTRING
, 0, (LPARAM
)szEnc
);
567 SendMessageW(hEncCombo
, CB_SETCURSEL
, (WPARAM
)Globals
.encOfnCombo
, 0);
572 if (LOWORD(wParam
) == IDC_OFN_ENCCOMBO
&&
573 HIWORD(wParam
) == CBN_SELCHANGE
)
575 int index
= SendMessageW(hEncCombo
, CB_GETCURSEL
, 0, 0);
576 Globals
.encOfnCombo
= index
==CB_ERR
? ENCODING_ANSI
: (ENCODING
)index
;
582 switch (((OFNOTIFYW
*)lParam
)->hdr
.code
)
585 if (Globals
.bOfnIsOpenDialog
)
587 /* Check the start of the selected file for a BOM. */
589 WCHAR szFileName
[MAX_PATH
];
590 SendMessageW(GetParent(hdlg
), CDM_GETFILEPATH
,
591 ARRAY_SIZE(szFileName
), (LPARAM
)szFileName
);
592 enc
= detect_encoding_of_file(szFileName
);
593 if (enc
!= ENCODING_AUTO
)
595 Globals
.encOfnCombo
= enc
;
596 SendMessageW(hEncCombo
, CB_SETCURSEL
, (WPARAM
)enc
, 0);
612 VOID
DIALOG_FileOpen(VOID
)
614 OPENFILENAMEW openfilename
;
615 WCHAR szPath
[MAX_PATH
];
616 static const WCHAR szDefaultExt
[] = { 't','x','t',0 };
617 static const WCHAR txt_files
[] = { '*','.','t','x','t',0 };
619 ZeroMemory(&openfilename
, sizeof(openfilename
));
621 lstrcpyW(szPath
, txt_files
);
623 openfilename
.lStructSize
= sizeof(openfilename
);
624 openfilename
.hwndOwner
= Globals
.hMainWnd
;
625 openfilename
.hInstance
= Globals
.hInstance
;
626 openfilename
.lpstrFilter
= Globals
.szFilter
;
627 openfilename
.lpstrFile
= szPath
;
628 openfilename
.nMaxFile
= ARRAY_SIZE(szPath
);
629 openfilename
.Flags
= OFN_ENABLETEMPLATE
| OFN_ENABLEHOOK
| OFN_EXPLORER
|
630 OFN_FILEMUSTEXIST
| OFN_PATHMUSTEXIST
|
631 OFN_HIDEREADONLY
| OFN_ENABLESIZING
;
632 openfilename
.lpfnHook
= OfnHookProc
;
633 openfilename
.lpTemplateName
= MAKEINTRESOURCEW(IDD_OFN_TEMPLATE
);
634 openfilename
.lpstrDefExt
= szDefaultExt
;
636 Globals
.encOfnCombo
= ENCODING_ANSI
;
637 Globals
.bOfnIsOpenDialog
= TRUE
;
639 if (GetOpenFileNameW(&openfilename
))
640 DoOpenFile(openfilename
.lpstrFile
, Globals
.encOfnCombo
);
643 /* Return FALSE to cancel close */
644 BOOL
DIALOG_FileSave(VOID
)
646 if (Globals
.szFileName
[0] == '\0')
647 return DIALOG_FileSaveAs();
650 switch (DoSaveFile(Globals
.szFileName
, Globals
.encFile
))
652 case SAVED_OK
: return TRUE
;
653 case SHOW_SAVEAS_DIALOG
: return DIALOG_FileSaveAs();
654 default: return FALSE
;
659 BOOL
DIALOG_FileSaveAs(VOID
)
661 OPENFILENAMEW saveas
;
662 WCHAR szPath
[MAX_PATH
];
663 static const WCHAR szDefaultExt
[] = { 't','x','t',0 };
664 static const WCHAR txt_files
[] = { '*','.','t','x','t',0 };
666 ZeroMemory(&saveas
, sizeof(saveas
));
668 lstrcpyW(szPath
, txt_files
);
670 saveas
.lStructSize
= sizeof(OPENFILENAMEW
);
671 saveas
.hwndOwner
= Globals
.hMainWnd
;
672 saveas
.hInstance
= Globals
.hInstance
;
673 saveas
.lpstrFilter
= Globals
.szFilter
;
674 saveas
.lpstrFile
= szPath
;
675 saveas
.nMaxFile
= ARRAY_SIZE(szPath
);
676 saveas
.Flags
= OFN_ENABLETEMPLATE
| OFN_ENABLEHOOK
| OFN_EXPLORER
|
677 OFN_PATHMUSTEXIST
| OFN_OVERWRITEPROMPT
|
678 OFN_HIDEREADONLY
| OFN_ENABLESIZING
;
679 saveas
.lpfnHook
= OfnHookProc
;
680 saveas
.lpTemplateName
= MAKEINTRESOURCEW(IDD_OFN_TEMPLATE
);
681 saveas
.lpstrDefExt
= szDefaultExt
;
683 /* Preset encoding to what file was opened/saved last with. */
684 Globals
.encOfnCombo
= Globals
.encFile
;
685 Globals
.bOfnIsOpenDialog
= FALSE
;
688 if (!GetSaveFileNameW(&saveas
))
691 switch (DoSaveFile(szPath
, Globals
.encOfnCombo
))
694 SetFileNameAndEncoding(szPath
, Globals
.encOfnCombo
);
695 UpdateWindowCaption();
698 case SHOW_SAVEAS_DIALOG
:
711 } TEXTINFO
, *LPTEXTINFO
;
713 static int notepad_print_header(HDC hdc
, RECT
*rc
, BOOL dopage
, BOOL header
, int page
, LPWSTR text
)
719 /* Write the header or footer */
720 GetTextExtentPoint32W(hdc
, text
, lstrlenW(text
), &szMetric
);
722 ExtTextOutW(hdc
, (rc
->left
+ rc
->right
- szMetric
.cx
) / 2,
723 header
? rc
->top
: rc
->bottom
- szMetric
.cy
,
724 ETO_CLIPPED
, rc
, text
, lstrlenW(text
), NULL
);
730 static WCHAR
*expand_header_vars(WCHAR
*pattern
, int page
)
735 WCHAR
*buffer
= NULL
;
737 for (i
= 0; pattern
[i
]; i
++)
741 if (pattern
[i
] == '&')
743 else if (pattern
[i
] == 'p')
747 else if (pattern
[i
] == '&')
753 buffer
= HeapAlloc(GetProcessHeap(), 0, (length
+ 1) * sizeof(WCHAR
));
758 for (i
= 0; pattern
[i
]; i
++)
762 if (pattern
[i
] == '&')
764 else if (pattern
[i
] == 'p')
766 static const WCHAR percent_dW
[] = {'%','d',0};
767 j
+= wnsprintfW(&buffer
[j
], 11, percent_dW
, page
);
771 else if (pattern
[i
] == '&')
774 buffer
[j
++] = pattern
[i
];
781 static BOOL
notepad_print_page(HDC hdc
, RECT
*rc
, BOOL dopage
, int page
, LPTEXTINFO tInfo
)
786 WCHAR
*footer_text
= NULL
;
788 footer_text
= expand_header_vars(Globals
.szFooter
, page
);
789 if (footer_text
== NULL
)
794 if (StartPage(hdc
) <= 0)
796 static const WCHAR failedW
[] = { 'S','t','a','r','t','P','a','g','e',' ','f','a','i','l','e','d',0 };
797 static const WCHAR errorW
[] = { 'P','r','i','n','t',' ','E','r','r','o','r',0 };
798 MessageBoxW(Globals
.hMainWnd
, failedW
, errorW
, MB_ICONEXCLAMATION
);
799 HeapFree(GetProcessHeap(), 0, footer_text
);
804 GetTextMetricsW(hdc
, &tm
);
805 y
= rc
->top
+ notepad_print_header(hdc
, rc
, dopage
, TRUE
, page
, Globals
.szFileName
) * tm
.tmHeight
;
806 b
= rc
->bottom
- 2 * notepad_print_header(hdc
, rc
, FALSE
, FALSE
, page
, footer_text
) * tm
.tmHeight
;
813 /* find the end of the line */
814 while (tInfo
->mptr
< tInfo
->mend
&& *tInfo
->mptr
!= '\n' && *tInfo
->mptr
!= '\r')
816 if (*tInfo
->mptr
== '\t')
818 /* replace tabs with spaces */
819 for (m
= 0; m
< SPACES_IN_TAB
; m
++)
821 if (tInfo
->len
< PRINT_LEN_MAX
)
822 tInfo
->lptr
[tInfo
->len
++] = ' ';
823 else if (Globals
.bWrapLongLines
)
827 else if (tInfo
->len
< PRINT_LEN_MAX
)
828 tInfo
->lptr
[tInfo
->len
++] = *tInfo
->mptr
;
830 if (tInfo
->len
>= PRINT_LEN_MAX
&& Globals
.bWrapLongLines
)
837 /* Find out how much we should print if line wrapping is enabled */
838 if (Globals
.bWrapLongLines
)
840 GetTextExtentExPointW(hdc
, tInfo
->lptr
, tInfo
->len
, rc
->right
- rc
->left
, &n
, NULL
, &szMetrics
);
841 if (n
< tInfo
->len
&& tInfo
->lptr
[n
] != ' ')
844 /* Don't wrap words unless it's a single word over the entire line */
845 while (m
&& tInfo
->lptr
[m
] != ' ') m
--;
846 if (m
> 0) n
= m
+ 1;
853 ExtTextOutW(hdc
, rc
->left
, y
, ETO_CLIPPED
, rc
, tInfo
->lptr
, n
, NULL
);
859 memcpy(tInfo
->lptr
, tInfo
->lptr
+ n
, tInfo
->len
* sizeof(WCHAR
));
860 y
+= tm
.tmHeight
+ tm
.tmExternalLeading
;
864 /* find the next line */
865 while (tInfo
->mptr
< tInfo
->mend
&& y
< b
&& (*tInfo
->mptr
== '\n' || *tInfo
->mptr
== '\r'))
867 if (*tInfo
->mptr
== '\n')
868 y
+= tm
.tmHeight
+ tm
.tmExternalLeading
;
872 } while (tInfo
->mptr
< tInfo
->mend
&& y
< b
);
874 notepad_print_header(hdc
, rc
, dopage
, FALSE
, page
, footer_text
);
879 HeapFree(GetProcessHeap(), 0, footer_text
);
883 VOID
DIALOG_FilePrint(VOID
)
887 int page
, dopage
, copy
;
889 HFONT hTextFont
, old_font
= 0;
895 WCHAR cTemp
[PRINT_LEN_MAX
];
897 /* Get Current Settings */
898 ZeroMemory(&printer
, sizeof(printer
));
899 printer
.lStructSize
= sizeof(printer
);
900 printer
.hwndOwner
= Globals
.hMainWnd
;
901 printer
.hDevMode
= Globals
.hDevMode
;
902 printer
.hDevNames
= Globals
.hDevNames
;
903 printer
.hInstance
= Globals
.hInstance
;
905 /* Set some default flags */
906 printer
.Flags
= PD_RETURNDC
| PD_NOSELECTION
;
907 printer
.nFromPage
= 0;
908 printer
.nMinPage
= 1;
909 /* we really need to calculate number of pages to set nMaxPage and nToPage */
911 printer
.nMaxPage
= -1;
912 /* Let commdlg manage copy settings */
913 printer
.nCopies
= (WORD
)PD_USEDEVMODECOPIES
;
915 if (!PrintDlgW(&printer
)) return;
917 Globals
.hDevMode
= printer
.hDevMode
;
918 Globals
.hDevNames
= printer
.hDevNames
;
920 SetMapMode(printer
.hDC
, MM_TEXT
);
922 /* initialize DOCINFO */
923 di
.cbSize
= sizeof(DOCINFOW
);
924 di
.lpszDocName
= Globals
.szFileTitle
;
925 di
.lpszOutput
= NULL
;
926 di
.lpszDatatype
= NULL
;
929 if(printer
.Flags
& PD_PRINTTOFILE
)
931 di
.lpszOutput
= dialog_print_to_file(printer
.hwndOwner
);
936 /* Get the file text */
937 size
= GetWindowTextLengthW(Globals
.hEdit
) + 1;
938 pTemp
= HeapAlloc(GetProcessHeap(), 0, size
* sizeof(WCHAR
));
941 DeleteDC(printer
.hDC
);
945 size
= GetWindowTextW(Globals
.hEdit
, pTemp
, size
);
947 if (StartDocW(printer
.hDC
, &di
) > 0)
949 /* Get the page margins in pixels. */
950 rc
.top
= MulDiv(Globals
.iMarginTop
, GetDeviceCaps(printer
.hDC
, LOGPIXELSY
), 2540) -
951 GetDeviceCaps(printer
.hDC
, PHYSICALOFFSETY
);
952 rc
.bottom
= GetDeviceCaps(printer
.hDC
, PHYSICALHEIGHT
) -
953 MulDiv(Globals
.iMarginBottom
, GetDeviceCaps(printer
.hDC
, LOGPIXELSY
), 2540);
954 rc
.left
= MulDiv(Globals
.iMarginLeft
, GetDeviceCaps(printer
.hDC
, LOGPIXELSX
), 2540) -
955 GetDeviceCaps(printer
.hDC
, PHYSICALOFFSETX
);
956 rc
.right
= GetDeviceCaps(printer
.hDC
, PHYSICALWIDTH
) -
957 MulDiv(Globals
.iMarginRight
, GetDeviceCaps(printer
.hDC
, LOGPIXELSX
), 2540);
959 /* Create a font for the printer resolution */
960 lfFont
= Globals
.lfFont
;
961 lfFont
.lfHeight
= MulDiv(lfFont
.lfHeight
, GetDeviceCaps(printer
.hDC
, LOGPIXELSY
), GetDpiForWindow(Globals
.hMainWnd
));
962 /* Make the font a bit lighter */
963 lfFont
.lfWeight
-= 100;
964 hTextFont
= CreateFontIndirectW(&lfFont
);
965 old_font
= SelectObject(printer
.hDC
, hTextFont
);
967 for (copy
= 1; copy
<= printer
.nCopies
; copy
++)
972 tInfo
.mend
= pTemp
+ size
;
977 if (printer
.Flags
& PD_PAGENUMS
)
979 /* a specific range of pages is selected, so
980 * skip pages that are not to be printed
982 if (page
> printer
.nToPage
)
984 else if (page
>= printer
.nFromPage
)
992 ret
= notepad_print_page(printer
.hDC
, &rc
, dopage
, page
, &tInfo
);
994 } while (ret
&& tInfo
.mptr
< tInfo
.mend
);
999 SelectObject(printer
.hDC
, old_font
);
1000 DeleteObject(hTextFont
);
1002 DeleteDC(printer
.hDC
);
1003 HeapFree(GetProcessHeap(), 0, pTemp
);
1006 VOID
DIALOG_FilePrinterSetup(VOID
)
1010 ZeroMemory(&printer
, sizeof(printer
));
1011 printer
.lStructSize
= sizeof(printer
);
1012 printer
.hwndOwner
= Globals
.hMainWnd
;
1013 printer
.hDevMode
= Globals
.hDevMode
;
1014 printer
.hDevNames
= Globals
.hDevNames
;
1015 printer
.hInstance
= Globals
.hInstance
;
1016 printer
.Flags
= PD_PRINTSETUP
;
1017 printer
.nCopies
= 1;
1019 PrintDlgW(&printer
);
1021 Globals
.hDevMode
= printer
.hDevMode
;
1022 Globals
.hDevNames
= printer
.hDevNames
;
1025 VOID
DIALOG_FileExit(VOID
)
1027 PostMessageW(Globals
.hMainWnd
, WM_CLOSE
, 0, 0l);
1030 VOID
DIALOG_EditUndo(VOID
)
1032 SendMessageW(Globals
.hEdit
, EM_UNDO
, 0, 0);
1035 VOID
DIALOG_EditCut(VOID
)
1037 SendMessageW(Globals
.hEdit
, WM_CUT
, 0, 0);
1040 VOID
DIALOG_EditCopy(VOID
)
1042 SendMessageW(Globals
.hEdit
, WM_COPY
, 0, 0);
1045 VOID
DIALOG_EditPaste(VOID
)
1047 SendMessageW(Globals
.hEdit
, WM_PASTE
, 0, 0);
1050 VOID
DIALOG_EditDelete(VOID
)
1052 SendMessageW(Globals
.hEdit
, WM_CLEAR
, 0, 0);
1055 VOID
DIALOG_EditSelectAll(VOID
)
1057 SendMessageW(Globals
.hEdit
, EM_SETSEL
, 0, -1);
1060 VOID
DIALOG_EditTimeDate(VOID
)
1063 WCHAR szDate
[MAX_STRING_LEN
];
1064 static const WCHAR spaceW
[] = { ' ',0 };
1068 GetTimeFormatW(LOCALE_USER_DEFAULT
, TIME_NOSECONDS
, &st
, NULL
, szDate
, MAX_STRING_LEN
);
1069 SendMessageW(Globals
.hEdit
, EM_REPLACESEL
, TRUE
, (LPARAM
)szDate
);
1071 SendMessageW(Globals
.hEdit
, EM_REPLACESEL
, TRUE
, (LPARAM
)spaceW
);
1073 GetDateFormatW(LOCALE_USER_DEFAULT
, 0, &st
, NULL
, szDate
, MAX_STRING_LEN
);
1074 SendMessageW(Globals
.hEdit
, EM_REPLACESEL
, TRUE
, (LPARAM
)szDate
);
1077 VOID
DIALOG_EditWrap(VOID
)
1079 BOOL modify
= FALSE
;
1080 static const WCHAR editW
[] = { 'e','d','i','t',0 };
1081 DWORD dwStyle
= WS_CHILD
| WS_VISIBLE
| WS_BORDER
| WS_VSCROLL
|
1082 ES_AUTOVSCROLL
| ES_MULTILINE
;
1087 size
= GetWindowTextLengthW(Globals
.hEdit
) + 1;
1088 pTemp
= HeapAlloc(GetProcessHeap(), 0, size
* sizeof(WCHAR
));
1094 GetWindowTextW(Globals
.hEdit
, pTemp
, size
);
1095 modify
= SendMessageW(Globals
.hEdit
, EM_GETMODIFY
, 0, 0);
1096 DestroyWindow(Globals
.hEdit
);
1097 GetClientRect(Globals
.hMainWnd
, &rc
);
1098 if( Globals
.bWrapLongLines
) dwStyle
|= WS_HSCROLL
| ES_AUTOHSCROLL
;
1099 Globals
.hEdit
= CreateWindowExW(WS_EX_CLIENTEDGE
, editW
, NULL
, dwStyle
,
1100 0, 0, rc
.right
, rc
.bottom
, Globals
.hMainWnd
,
1101 NULL
, Globals
.hInstance
, NULL
);
1102 SendMessageW(Globals
.hEdit
, WM_SETFONT
, (WPARAM
)Globals
.hFont
, FALSE
);
1103 SetWindowTextW(Globals
.hEdit
, pTemp
);
1104 SendMessageW(Globals
.hEdit
, EM_SETMODIFY
, modify
, 0);
1105 SetFocus(Globals
.hEdit
);
1106 HeapFree(GetProcessHeap(), 0, pTemp
);
1108 Globals
.bWrapLongLines
= !Globals
.bWrapLongLines
;
1109 CheckMenuItem(GetMenu(Globals
.hMainWnd
), CMD_WRAP
,
1110 MF_BYCOMMAND
| (Globals
.bWrapLongLines
? MF_CHECKED
: MF_UNCHECKED
));
1111 SetWindowSubclass(Globals
.hEdit
, EDIT_CallBackProc
, 0, 0);
1112 updateWindowSize(rc
.right
, rc
.bottom
);
1115 VOID
DIALOG_SelectFont(VOID
)
1118 LOGFONTW lf
=Globals
.lfFont
;
1120 ZeroMemory( &cf
, sizeof(cf
) );
1121 cf
.lStructSize
=sizeof(cf
);
1122 cf
.hwndOwner
=Globals
.hMainWnd
;
1124 cf
.Flags
=CF_SCREENFONTS
| CF_INITTOLOGFONTSTRUCT
| CF_NOVERTFONTS
;
1126 if( ChooseFontW(&cf
) )
1128 HFONT currfont
=Globals
.hFont
;
1130 Globals
.hFont
=CreateFontIndirectW( &lf
);
1132 SendMessageW( Globals
.hEdit
, WM_SETFONT
, (WPARAM
)Globals
.hFont
, TRUE
);
1133 if( currfont
!=NULL
)
1134 DeleteObject( currfont
);
1138 VOID
DIALOG_Search(VOID
)
1140 /* Allow only one search/replace dialog to open */
1141 if(Globals
.hFindReplaceDlg
!= NULL
)
1143 SetActiveWindow(Globals
.hFindReplaceDlg
);
1147 ZeroMemory(&Globals
.find
, sizeof(Globals
.find
));
1148 Globals
.find
.lStructSize
= sizeof(Globals
.find
);
1149 Globals
.find
.hwndOwner
= Globals
.hMainWnd
;
1150 Globals
.find
.hInstance
= Globals
.hInstance
;
1151 Globals
.find
.lpstrFindWhat
= Globals
.szFindText
;
1152 Globals
.find
.wFindWhatLen
= ARRAY_SIZE(Globals
.szFindText
);
1153 Globals
.find
.Flags
= FR_DOWN
|FR_HIDEWHOLEWORD
;
1155 /* We only need to create the modal FindReplace dialog which will */
1156 /* notify us of incoming events using hMainWnd Window Messages */
1158 Globals
.hFindReplaceDlg
= FindTextW(&Globals
.find
);
1159 assert(Globals
.hFindReplaceDlg
!=0);
1162 VOID
DIALOG_SearchNext(VOID
)
1164 if (Globals
.lastFind
.lpstrFindWhat
== NULL
)
1166 else /* use the last find data */
1167 NOTEPAD_DoFind(&Globals
.lastFind
);
1170 VOID
DIALOG_Replace(VOID
)
1172 /* Allow only one search/replace dialog to open */
1173 if(Globals
.hFindReplaceDlg
!= NULL
)
1175 SetActiveWindow(Globals
.hFindReplaceDlg
);
1179 ZeroMemory(&Globals
.find
, sizeof(Globals
.find
));
1180 Globals
.find
.lStructSize
= sizeof(Globals
.find
);
1181 Globals
.find
.hwndOwner
= Globals
.hMainWnd
;
1182 Globals
.find
.hInstance
= Globals
.hInstance
;
1183 Globals
.find
.lpstrFindWhat
= Globals
.szFindText
;
1184 Globals
.find
.wFindWhatLen
= ARRAY_SIZE(Globals
.szFindText
);
1185 Globals
.find
.lpstrReplaceWith
= Globals
.szReplaceText
;
1186 Globals
.find
.wReplaceWithLen
= ARRAY_SIZE(Globals
.szReplaceText
);
1187 Globals
.find
.Flags
= FR_DOWN
|FR_HIDEWHOLEWORD
;
1189 /* We only need to create the modal FindReplace dialog which will */
1190 /* notify us of incoming events using hMainWnd Window Messages */
1192 Globals
.hFindReplaceDlg
= ReplaceTextW(&Globals
.find
);
1193 assert(Globals
.hFindReplaceDlg
!=0);
1196 VOID
DIALOG_HelpContents(VOID
)
1198 WinHelpW(Globals
.hMainWnd
, helpfileW
, HELP_INDEX
, 0);
1201 VOID
DIALOG_HelpAboutNotepad(VOID
)
1203 static const WCHAR notepadW
[] = { 'W','i','n','e',' ','N','o','t','e','p','a','d',0 };
1204 WCHAR szNotepad
[MAX_STRING_LEN
];
1205 HICON icon
= LoadImageW(Globals
.hInstance
, MAKEINTRESOURCEW(IDI_NOTEPAD
),
1206 IMAGE_ICON
, 48, 48, LR_SHARED
);
1208 LoadStringW(Globals
.hInstance
, STRING_NOTEPAD
, szNotepad
, ARRAY_SIZE(szNotepad
));
1209 ShellAboutW(Globals
.hMainWnd
, szNotepad
, notepadW
, icon
);
1213 /***********************************************************************
1215 * DIALOG_FilePageSetup
1217 VOID
DIALOG_FilePageSetup(void)
1219 DialogBoxW(Globals
.hInstance
, MAKEINTRESOURCEW(DIALOG_PAGESETUP
),
1220 Globals
.hMainWnd
, DIALOG_PAGESETUP_DlgProc
);
1224 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1226 * DIALOG_PAGESETUP_DlgProc
1229 static INT_PTR WINAPI
DIALOG_PAGESETUP_DlgProc(HWND hDlg
, UINT msg
, WPARAM wParam
, LPARAM lParam
)
1238 /* save user input and close dialog */
1239 GetDlgItemTextW(hDlg
, IDC_PAGESETUP_HEADERVALUE
, Globals
.szHeader
, ARRAY_SIZE(Globals
.szHeader
));
1240 GetDlgItemTextW(hDlg
, IDC_PAGESETUP_FOOTERVALUE
, Globals
.szFooter
, ARRAY_SIZE(Globals
.szFooter
));
1242 Globals
.iMarginTop
= GetDlgItemInt(hDlg
, IDC_PAGESETUP_TOPVALUE
, NULL
, FALSE
) * 100;
1243 Globals
.iMarginBottom
= GetDlgItemInt(hDlg
, IDC_PAGESETUP_BOTTOMVALUE
, NULL
, FALSE
) * 100;
1244 Globals
.iMarginLeft
= GetDlgItemInt(hDlg
, IDC_PAGESETUP_LEFTVALUE
, NULL
, FALSE
) * 100;
1245 Globals
.iMarginRight
= GetDlgItemInt(hDlg
, IDC_PAGESETUP_RIGHTVALUE
, NULL
, FALSE
) * 100;
1246 EndDialog(hDlg
, IDOK
);
1250 /* discard user input and close dialog */
1251 EndDialog(hDlg
, IDCANCEL
);
1256 /* FIXME: Bring this to work */
1257 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 };
1258 static const WCHAR helpW
[] = { 'H','e','l','p',0 };
1259 MessageBoxW(Globals
.hMainWnd
, sorryW
, helpW
, MB_ICONEXCLAMATION
);
1269 /* fetch last user input prior to display dialog */
1270 SetDlgItemTextW(hDlg
, IDC_PAGESETUP_HEADERVALUE
, Globals
.szHeader
);
1271 SetDlgItemTextW(hDlg
, IDC_PAGESETUP_FOOTERVALUE
, Globals
.szFooter
);
1272 SetDlgItemInt(hDlg
, IDC_PAGESETUP_TOPVALUE
, Globals
.iMarginTop
/ 100, FALSE
);
1273 SetDlgItemInt(hDlg
, IDC_PAGESETUP_BOTTOMVALUE
, Globals
.iMarginBottom
/ 100, FALSE
);
1274 SetDlgItemInt(hDlg
, IDC_PAGESETUP_LEFTVALUE
, Globals
.iMarginLeft
/ 100, FALSE
);
1275 SetDlgItemInt(hDlg
, IDC_PAGESETUP_RIGHTVALUE
, Globals
.iMarginRight
/ 100, FALSE
);
1282 static INT_PTR WINAPI
DIALOG_GOTO_DlgProc(HWND hDlg
, UINT msg
, WPARAM wParam
, LPARAM lParam
)
1292 int lineValue
= GetDlgItemInt(hDlg
, IDC_GOTO_LINEVALUE
, NULL
, FALSE
) - 1;
1293 long lineIndex
= SendMessageW(Globals
.hEdit
, EM_LINEINDEX
, lineValue
, 0);
1295 SendMessageW(Globals
.hEdit
, EM_SETSEL
, lineIndex
, lineIndex
);
1297 EndDialog(hDlg
, IDOK
);
1302 EndDialog(hDlg
, IDCANCEL
);
1314 int currentLine
= SendMessageW(Globals
.hEdit
, EM_LINEFROMCHAR
, -1, 0) + 1;
1315 SetDlgItemInt(hDlg
, IDC_GOTO_LINEVALUE
, currentLine
, FALSE
);
1322 void DIALOG_EditGoTo(void)
1324 DialogBoxW(Globals
.hInstance
, MAKEINTRESOURCEW(DIALOG_GOTO
),
1325 Globals
.hMainWnd
, DIALOG_GOTO_DlgProc
);