5#include "Stroika/Foundation/StroikaPreComp.h"
11#include "Stroika/Foundation/Characters/LineEndings.h"
14#include "Stroika/Frameworks/Led/FlavorPackage.h"
16#include "LedLineItServerItem.h"
19#include "LedLineItDocument.h"
24using namespace Stroika::Foundation::Streams;
25using namespace Stroika::Frameworks::Led;
27using Stroika::Foundation::Characters::CodePagePrettyNameMapper;
32#ifndef _AFX_OLD_EXCEPTIONS
33#define DELETE_EXCEPTION(e) \
38#define DELETE_EXCEPTION(e)
41static void AppendFilterSuffix (CString& filter, OPENFILENAME& ofn, CString strFilterExt, CString strFilterName);
42static void AppendFilterSuffix (CString& filter, OPENFILENAME& ofn, CDocTemplate* pTemplate);
44static SDKString MapCodePageToPrettyName (CodePage cp)
47 case kAutomaticallyGuessCodePage:
48 return Led_SDK_TCHAROF (
"Automaticly Detect");
50 return CodePagePrettyNameMapper::GetName (cp);
54static bool ShuffleToFront (vector<CodePage>* codePages, CodePage cp)
56 vector<CodePage>::iterator i = std::find (codePages->begin (), codePages->end (), cp);
57 if (i != codePages->end ()) {
59 codePages->insert (codePages->begin (), cp);
65class FileDialogWithCodePage :
public CFileDialog {
67 using inherited = CFileDialog;
70 FileDialogWithCodePage (
bool asOpenDialog,
const vector<CodePage>& codePages, CodePage initialCodePage)
71 : CFileDialog{asOpenDialog}
72 , fCodePages{codePages}
73 , fCodePage{initialCodePage}
75 m_ofn.Flags |= OFN_ENABLETEMPLATE | OFN_EXPLORER;
76 m_ofn.lpTemplateName = m_lpszTemplateName = MAKEINTRESOURCE (kFileDialogAddOnID);
79 virtual BOOL OnInitDialog ()
override
81 inherited::OnInitDialog ();
82 fCodePageComboBox.SubclassWindow (::GetDlgItem (GetSafeHwnd (), kFileDialog_EncodingComboBox));
83 for (vector<CodePage>::const_iterator i = fCodePages.begin (); i != fCodePages.end (); ++i) {
84 fCodePageComboBox.AddString (MapCodePageToPrettyName (*i).c_str ());
85 if (*i == fCodePage) {
86 fCodePageComboBox.SetCurSel (
static_cast<int> (i - fCodePages.begin ()));
95 afx_msg
void OnCodePageSelChange ()
97 int curSel = fCodePageComboBox.GetCurSel ();
98 if (curSel != CB_ERR) {
99 Assert (curSel <
static_cast<int> (fCodePages.size ()));
100 fCodePage = fCodePages[curSel];
105 DECLARE_MESSAGE_MAP ()
108 vector<CodePage> fCodePages;
109 CComboBox fCodePageComboBox;
112BEGIN_MESSAGE_MAP (FileDialogWithCodePage, CFileDialog)
113ON_CBN_SELCHANGE (kFileDialog_EncodingComboBox, OnCodePageSelChange)
117 class LineTooLongOnReadDialog :
public CDialog {
119 LineTooLongOnReadDialog (
const SDKString& message,
size_t breakCount)
120 : CDialog{kLineTooLongOnRead_DialogID}
122 , fBreakCount{breakCount}
125 virtual BOOL OnInitDialog ()
override
127 BOOL result = CDialog::OnInitDialog ();
128 Led_CenterWindowInParent (m_hWnd);
129 SetDlgItemText (kLineTooLongOnRead_Dialog_MessageFieldID, fMessage.c_str ());
130 SetDlgItemInt (kLineTooLongOnRead_Dialog_BreakNumFieldID,
static_cast<UINT
> (fBreakCount));
133 virtual void OnOK ()
override
135 size_t origBreakCount = fBreakCount;
137 fBreakCount = GetDlgItemInt (kLineTooLongOnRead_Dialog_BreakNumFieldID, &trans);
139 fBreakCount = origBreakCount;
151 DECLARE_MESSAGE_MAP ()
153 BEGIN_MESSAGE_MAP (LineTooLongOnReadDialog, CDialog)
162CodePage LedLineItDocument::sHiddenDocOpenArg = kIGNORECodePage;
164IMPLEMENT_DYNCREATE (LedLineItDocument, COleServerDoc)
166BEGIN_MESSAGE_MAP (LedLineItDocument, COleServerDoc)
167ON_UPDATE_COMMAND_UI (ID_EDIT_PASTE_LINK, OnUpdatePasteLinkMenu)
168ON_UPDATE_COMMAND_UI (ID_OLE_EDIT_CONVERT, OnUpdateObjectVerbMenu)
169ON_COMMAND (ID_OLE_EDIT_CONVERT, OnEditConvert)
170ON_UPDATE_COMMAND_UI (ID_OLE_EDIT_LINKS, OnUpdateEditLinksMenu)
171ON_COMMAND (ID_OLE_EDIT_LINKS, OnEditLinks)
172ON_UPDATE_COMMAND_UI (ID_OLE_VERB_FIRST, OnUpdateObjectVerbMenu)
173ON_UPDATE_COMMAND_UI (ID_FILE_SAVE, OnUpdateFileSave)
174ON_COMMAND (ID_FILE_SAVE_COPY_AS, OnFileSaveCopyAs)
177BEGIN_DISPATCH_MAP (LedLineItDocument, COleServerDoc)
185static const IID IID_ILedLineIt = {0xfc00622, 0x28bd, 0x11cf, {0x89, 0x9c, 0x0, 0xaa, 0x0, 0x58, 0x3, 0x24}};
187BEGIN_INTERFACE_MAP (LedLineItDocument, COleServerDoc)
188INTERFACE_PART (LedLineItDocument, IID_ILedLineIt, Dispatch)
191LedLineItDocument::LedLineItDocument ()
195 , fCommandHandler{kMaxNumUndoLevels}
196 , fCodePage{kDefaultNewDocCodePage}
201 fTextStore.SetTextBreaker (shared_ptr<TextBreaks> (
new TextBreaks_Basic_TextEditor ()));
202 fTextStore.AddMarkerOwner (
this);
205LedLineItDocument::~LedLineItDocument ()
207 fTextStore.RemoveMarkerOwner (
this);
211void LedLineItDocument::DidUpdateText (
const UpdateInfo& updateInfo)
noexcept
213 if (updateInfo.fRealContentUpdate) {
218TextStore* LedLineItDocument::PeekAtTextStore ()
const
220 return &
const_cast<LedLineItDocument*
> (
this)->fTextStore;
223BOOL LedLineItDocument::OnNewDocument ()
225 fCommandHandler.Commit ();
226 if (!COleServerDoc::OnNewDocument ()) {
232COleServerItem* LedLineItDocument::OnGetEmbeddedItem ()
236 LedLineItServerItem* pItem =
new LedLineItServerItem (
this);
237 ASSERT_VALID (pItem);
241BOOL LedLineItDocument::DoSave (LPCTSTR lpszPathName, BOOL bReplace)
244 CString newName = lpszPathName;
245 if (newName.IsEmpty ()) {
246 CDocTemplate* pTemplate = GetDocTemplate ();
247 ASSERT (pTemplate != NULL);
248 newName = m_strPathName;
249 if (bReplace && newName.IsEmpty ()) {
250 newName = m_strTitle;
252 int iBad = newName.FindOneOf (_T(
" #%;/\\"));
254 newName.ReleaseBuffer (iBad);
258 if (not newName.IsEmpty ()) {
259 newName += _T (
".txt");
264 if (not DoPromptSaveAsFileName (&newName, &useCodePage)) {
269 if (not DoPromptSaveCopyAsFileName (&newName, &useCodePage)) {
278 fCodePage = useCodePage;
280 if (!OnSaveDocument (newName)) {
281 if (lpszPathName == NULL) {
285 CFile::Remove (newName);
289 TRACE0 (
"Warning: failed to delete file after failed SaveAs.\n");
290 DELETE_EXCEPTION (e);
295 fCodePage = savedCodePage;
300 fCodePage = savedCodePage;
305 fCodePage = savedCodePage;
312 SetPathName (newName);
318BOOL LedLineItDocument::OnOpenDocument (LPCTSTR lpszPathName)
336 fCommandHandler.Commit ();
348 MyFlavorPackageInternalizer (TextStore& ts)
350 , fBreakLongLines{false}
351 , fBreakWidths{kMaxLineSize}
356 virtual void InternalizeFlavor_FILEGuessFormatsFromStartOfData ([[maybe_unused]] Led_ClipFormat* suggestedClipFormat,
357 optional<CodePage> suggestedCodePage,
const byte* fileStart,
358 const byte* fileEnd)
override
360 size_t curLineSize = 0;
361 size_t maxLineSize = 0;
362 for (
const byte* p = fileStart; p != fileEnd; ++p) {
363 if (to_integer<char> (*p) ==
'\n' or to_integer<char> (*p) ==
'\r') {
369 maxLineSize = max (maxLineSize, curLineSize);
371 if (suggestedCodePage and (suggestedCodePage == Characters::WellKnownCodePages::kUNICODE_WIDE or
372 suggestedCodePage == Characters::WellKnownCodePages::kUNICODE_WIDE_BIGENDIAN)) {
379 if (maxLineSize > kMaxLineSize) {
380 LineTooLongOnReadDialog dlg (
381 Characters::CString::Format (
382 Led_SDK_TCHAROF (
"This file contains at least one very long line (approximately %d characters). This may reduce "
383 "editor performance, and make viewing the file awkward. Long lines can optionally be "
384 "automatically broken up if they exceed the 'Break at characer count' value below."),
385 maxLineSize / 100 * 100),
387 fBreakLongLines = (dlg.DoModal () == IDOK);
388 fBreakWidths = dlg.fBreakCount;
392 virtual bool InternalizeFlavor_FILEDataRawBytes (Led_ClipFormat* suggestedClipFormat, optional<CodePage> suggestedCodePage,
393 size_t from,
size_t to,
const void* rawBytes,
size_t nRawBytes)
override
395 Led_ClipFormat cf = (suggestedClipFormat == NULL or *suggestedClipFormat == kBadClipFormat) ? kTEXTClipFormat : *suggestedClipFormat;
396 Require (cf == kTEXTClipFormat);
398 fBreakWidths = max<size_t> (fBreakWidths, 1U);
400 if (fBreakLongLines) {
403 Memory::BLOB rawBytesBLOB{span{
reinterpret_cast<const byte*
> (rawBytes), nRawBytes}};
406 : BinaryToText::Reader::New (rawBytesBLOB).ReadAll ();
408 Led_tString tx = x.
As<Led_tString> ();
409 size_t charsRead = tx.length ();
410 const auto fileData2 = span{tx};
412 StackBuffer<Led_tChar> patchedData{Memory::eUninitialized, charsRead + charsRead / fBreakWidths};
413 size_t curLineSize = 0;
415 for (
const Led_tChar* p = fileData2.data (); p != fileData2.data () + charsRead; ++p) {
416 if (*p ==
'\n' or *p ==
'\r') {
422 patchedData[ourIdx++] = *p;
423 if (curLineSize >= fBreakWidths) {
425 patchedData[ourIdx++] =
'\n';
429 GetTextStore ().Replace (from, to, patchedData.data (), charsRead);
434 return inherited::InternalizeFlavor_FILEDataRawBytes (suggestedClipFormat, suggestedCodePage, from, to, rawBytes, nRawBytes);
439 bool fBreakLongLines;
443 MyFlavorPackageInternalizer internalizer{fTextStore};
446 if (LedLineItDocument::sHiddenDocOpenArg != kIGNORECodePage) {
447 useCodePage = sHiddenDocOpenArg;
449 Led_ClipFormat cf = kTEXTClipFormat;
450 internalizer.InternalizeFlavor_FILEData (lpszPathName, &cf,
451 useCodePage == kAutomaticallyGuessCodePage ? optional<CodePage>{} : optional<CodePage>{useCodePage},
452 0, fTextStore.GetEnd ());
453 fCodePage = useCodePage;
455 if (not internalizer.fBreakLongLines) {
456 SetModifiedFlag (FALSE);
461void LedLineItDocument::Serialize (CArchive& ar)
463 if (ar.IsStoring ()) {
464 constexpr size_t kBufSize = 8 * 1024;
465 Led_tChar buf[kBufSize];
467 size_t eob = fTextStore.GetLength ();
468 if (fCodePage == Characters::WellKnownCodePages::kUNICODE_WIDE or
469 fCodePage == Characters::WellKnownCodePages::kUNICODE_WIDE_BIGENDIAN or fCodePage == Characters::WellKnownCodePages::kUTF8) {
472 case Characters::WellKnownCodePages::kUNICODE_WIDE:
473 ar.Write (Characters::GetByteOrderMark (Characters::UnicodeExternalEncodings::eUTF16).data (),
474 static_cast<UINT
> (Characters::GetByteOrderMark (Characters::UnicodeExternalEncodings::eUTF16).size ()));
476 case Characters::WellKnownCodePages::kUNICODE_WIDE_BIGENDIAN:
477 ar.Write (Characters::GetByteOrderMark (Characters::UnicodeExternalEncodings::eUTF16_BE).data (),
478 static_cast<UINT
> (Characters::GetByteOrderMark (Characters::UnicodeExternalEncodings::eUTF16_BE).size ()));
480 case Characters::WellKnownCodePages::kUTF8:
481 ar.Write (Characters::GetByteOrderMark (Characters::UnicodeExternalEncodings::eUTF8).data (),
482 static_cast<UINT
> (Characters::GetByteOrderMark (Characters::UnicodeExternalEncodings::eUTF8).size ()));
487 while (offset < eob) {
488 size_t charsToWrite = min (kBufSize, eob - offset);
489 fTextStore.CopyOut (offset, charsToWrite, buf);
490 offset += charsToWrite;
491#if qStroika_Foundation_Common_Platform_Windows
492 Led_tChar buf2[2 *
sizeof (buf)];
494 Led_tChar buf2[
sizeof (buf)];
496 charsToWrite = Characters::NLToNative<Led_tChar> (buf, charsToWrite, buf2,
sizeof (buf2));
497 StackBuffer<byte> buf3_{Memory::eUninitialized, codeCvt.ComputeTargetByteBufferSize (span{buf, charsToWrite})};
498 auto toWrite = codeCvt.Characters2Bytes (span{buf, charsToWrite}, span{buf3_});
499 char* buffp =
reinterpret_cast<char*
> (buf3_.data ());
500 size_t nBytesToWrite = toWrite.size ();
501 ar.Write (buffp,
static_cast<UINT
> (nBytesToWrite));
505 CFile* file = ar.GetFile ();
507 DWORD nLen =
static_cast<DWORD
> (file->GetLength ());
508 StackBuffer<char> buf{Memory::eUninitialized, nLen};
509 if (ar.Read (buf.data (), nLen) != nLen) {
510 AfxThrowArchiveException (CArchiveException::endOfFile);
514 size_t bytesToStrip = 0;
515 optional<Characters::UnicodeExternalEncodings> useUnicodEncoding;
516 if (LedLineItDocument::sHiddenDocOpenArg != kIGNORECodePage) {
517 useCodePage = sHiddenDocOpenArg;
518 if (useCodePage == kAutomaticallyGuessCodePage) {
519 optional<tuple<Characters::UnicodeExternalEncodings, size_t>> n =
520 Characters::ReadByteOrderMark (span{
reinterpret_cast<const byte*
> (buf.data ()), nLen});
522 bytesToStrip = get<size_t> (*n);
523 useUnicodEncoding = get<Characters::UnicodeExternalEncodings> (*n);
529 CodeCvt<Led_tChar> codeCvt{useUnicodEncoding ? CodeCvt<Led_tChar>{*useUnicodEncoding}
530 : (useCodePage == kAutomaticallyGuessCodePage ? CodeCvt<Led_tChar>{locale{}}
531 : CodeCvt<Led_tChar>{useCodePage})};
532 StackBuffer<Led_tChar> result{Memory::eUninitialized,
533 codeCvt.ComputeTargetCharacterBufferSize (span{
reinterpret_cast<const byte*
> (buf.data ()), nLen}) + 1};
534 span<Led_tChar> n = codeCvt.Bytes2Characters (span{
reinterpret_cast<const byte*
> (buf.data ()), nLen}.subspan (bytesToStrip), span{result});
535 nLen =
static_cast<DWORD
> (n.size ());
537 Led_tChar* buffp =
static_cast<Led_tChar*
> (result);
539 nLen =
static_cast<DWORD
> (Characters::NormalizeTextToNL<Led_tChar> (buffp, nLen, buffp, nLen));
540 fTextStore.Replace (0, 0, buffp, nLen);
542 fCodePage = useCodePage;
546void LedLineItDocument::OnUpdateFileSave (CCmdUI* pCmdUI)
551 pCmdUI->Enable (IsModified () or GetPathName ().GetLength () == 0);
554void LedLineItDocument::OnFileSaveCopyAs ()
557 Assert (m_bRemember);
559 LPSTORAGE savedStorage = m_lpRootStg;
563 DoSave (NULL,
false);
566 m_lpRootStg = savedStorage;
571 m_lpRootStg = savedStorage;
575void LedLineItDocument::DeleteContents ()
577 fTextStore.Replace (fTextStore.GetStart (), fTextStore.GetEnd (), LED_TCHAR_OF (
""), 0);
580bool LedLineItDocument::DoPromptSaveAsFileName (CString* fileName, CodePage* codePage)
582 return DoPromptFileName (fileName, AFX_IDS_SAVEFILE,
false, OFN_HIDEREADONLY | OFN_PATHMUSTEXIST, codePage);
585bool LedLineItDocument::DoPromptSaveCopyAsFileName (CString* fileName, CodePage* codePage)
587 return DoPromptFileName (fileName, AFX_IDS_SAVEFILECOPY,
false, OFN_HIDEREADONLY | OFN_PATHMUSTEXIST, codePage);
590bool LedLineItDocument::DoPromptOpenFileName (CString* fileName, CodePage* codePage)
592 return DoPromptFileName (fileName, AFX_IDS_OPENFILE,
true, OFN_HIDEREADONLY | OFN_FILEMUSTEXIST, codePage);
595bool LedLineItDocument::DoPromptFileName (CString* fileName, UINT nIDSTitle,
bool isOpenDialogCall,
long fileDLogFlags, CodePage* codePage)
597 vector<CodePage> codePages = CodePagesInstalled{}.GetAll ();
600 Assert (std::find (codePages.begin (), codePages.end (), kAutomaticallyGuessCodePage) == codePages.end ());
601 Assert (std::find (codePages.begin (), codePages.end (), kIGNORECodePage) == codePages.end ());
605 if (not isOpenDialogCall) {
609 vector<CodePage>::iterator i = std::find (codePages.begin (), codePages.end (), *codePage);
610 if (i == codePages.end ()) {
611 codePages.push_back (*codePage);
615 sort (codePages.begin (), codePages.end ());
620 (void)ShuffleToFront (&codePages, Characters::WellKnownCodePages::kUNICODE_WIDE_BIGENDIAN);
621 (void)ShuffleToFront (&codePages, Characters::WellKnownCodePages::kUNICODE_WIDE);
622 (void)ShuffleToFront (&codePages, Characters::WellKnownCodePages::kUTF8);
623 if (isOpenDialogCall) {
624 codePages.insert (codePages.begin (), kAutomaticallyGuessCodePage);
627 FileDialogWithCodePage dlgFile (isOpenDialogCall, codePages, isOpenDialogCall ? kAutomaticallyGuessCodePage : *codePage);
630 Verify (title.LoadString (nIDSTitle));
632 dlgFile.m_ofn.Flags |= fileDLogFlags;
635 AppendFilterSuffix (strFilter, dlgFile.m_ofn,
".txt",
"Text Files (*.txt)");
636 AppendFilterSuffix (strFilter, dlgFile.m_ofn,
".*",
"All Files (*.*)");
638 strFilter += (TCHAR)
'\0';
640 dlgFile.m_ofn.lpstrFilter = strFilter;
641 dlgFile.m_ofn.lpstrTitle = title;
643 dlgFile.m_ofn.lpstrFile = fileName->GetBuffer (_MAX_PATH);
645 dlgFile.m_ofn.nFilterIndex = 2;
646 bool bResult = (dlgFile.DoModal () == IDOK);
647 fileName->ReleaseBuffer ();
648 *codePage = dlgFile.fCodePage;
652#if qStroika_Foundation_Debug_AssertionsChecked
653void LedLineItDocument::AssertValid ()
const
655 COleServerDoc::AssertValid ();
656 fTextStore.Invariant ();
668static void AppendFilterSuffix (CString& filter, OPENFILENAME& ofn, CString strFilterExt, CString strFilterName)
670 Require (not strFilterExt.IsEmpty ());
671 Require (not strFilterName.IsEmpty ());
674 filter += strFilterName;
675 ASSERT (!filter.IsEmpty ());
676 filter += (TCHAR)
'\0';
677 filter += (TCHAR)
'*';
678 filter += strFilterExt;
679 filter += (TCHAR)
'\0';
680 ofn.nMaxCustFilter++;
683static void AppendFilterSuffix (CString& filter, OPENFILENAME& ofn, CDocTemplate* pTemplate)
685 ASSERT_VALID (pTemplate);
686 ASSERT_KINDOF (CDocTemplate, pTemplate);
687 CString strFilterExt;
688 CString strFilterName;
689 if (pTemplate->GetDocString (strFilterExt, CDocTemplate::filterExt) && !strFilterExt.IsEmpty () &&
690 pTemplate->GetDocString (strFilterName, CDocTemplate::filterName) && !strFilterName.IsEmpty ()) {
691 AppendFilterSuffix (filter, ofn, strFilterExt, strFilterName);
#define qStroika_Foundation_Debug_AssertionsChecked
The qStroika_Foundation_Debug_AssertionsChecked flag determines if assertions are checked and validat...
#define RequireNotNull(p)
CodeCvt unifies byte <-> unicode conversions, vaguely inspired by (and wraps) std::codecvt,...
String is like std::u32string, except it is much easier to use, often much more space efficient,...
nonvirtual String NormalizeTextToNL() const
Logically halfway between std::array and std::vector; Smart 'direct memory array' - which when needed...
basic_string< SDKChar > SDKString
Ptr New(const InputStream::Ptr< byte > &src, optional< AutomaticCodeCvtFlags > codeCvtFlags={}, optional< SeekableFlag > seekable={}, ReadAhead readAhead=eReadAheadAllowed)
Create an InputStream::Ptr<Character> from the arguments (usually binary source) - which can be used ...