Stroika Library 3.0d25
 
Loading...
Searching...
No Matches
FlavorPackage.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#include "Stroika/Frameworks/StroikaPreComp.h"
5
8#include "Stroika/Foundation/Characters/LineEndings.h"
11
12#include "Stroika/Frameworks/Led/Config.h"
13
14#if qStroika_Platform_Windows
15#include <fcntl.h>
16#include <io.h>
17#include <shellapi.h>
18#endif
19
20#include "Stroika/Frameworks/Led/Marker.h"
21#include "Stroika/Frameworks/Led/TextStore.h"
22
23#include "FlavorPackage.h"
24
25using std::byte;
26
27using namespace Stroika::Foundation;
29
30using namespace Stroika::Frameworks;
31using namespace Stroika::Frameworks::Led;
32
33/*
34 ********************************************************************************
35 *************************** FlavorPackageExternalizer **************************
36 ********************************************************************************
37 */
38
39TextStore* FlavorPackageExternalizer::PeekAtTextStore () const
40{
41 return &fTextStore;
42}
43
44/*
45@METHOD: FlavorPackageExternalizer::ExternalizeFlavors
46@DESCRIPTION: <p>Externalize to the given @'FlavorPackageExternalizer::WriterFlavorPackage' the text in the
47 given range. This can be for a Drag&Drop package, a ClipBoard package, or whatever
48 (see @'FlavorPackageExternalizer::ExternalizeBestFlavor').</p>
49*/
50void FlavorPackageExternalizer::ExternalizeFlavors (WriterFlavorPackage& flavorPackage, size_t from, size_t to)
51{
52 ExternalizeFlavor_TEXT (flavorPackage, from, to);
53}
54
55/*
56@METHOD: FlavorPackageExternalizer::ExternalizeBestFlavor
57@DESCRIPTION: <p>Externalize to the given @'FlavorPackageExternalizer::WriterFlavorPackage' the text in the
58 given range. This can be for a Drag&Drop package, a ClipBoard package, or whatever
59 (see @'FlavorPackageExternalizer::ExternalizeFlavors').</p>
60*/
61void FlavorPackageExternalizer::ExternalizeBestFlavor (WriterFlavorPackage& flavorPackage, size_t from, size_t to)
62{
63 ExternalizeFlavor_TEXT (flavorPackage, from, to);
64}
65
66/*
67@METHOD: FlavorPackageExternalizer::ExternalizeFlavor_TEXT
68@DESCRIPTION: <p>Externalize to the given @'FlavorPackageExternalizer::WriterFlavorPackage' the text in the
69 given range. This can be for a Drag&Drop package, a ClipBoard package, or whatever
70 (see @'FlavorPackageExternalizer::ExternalizeFlavors' and @'FlavorPackageExternalizer::ExternalizeBestFlavor').</p>
71 <p>This method externalizes in the native OS text format (with any embedded NUL-characters
72 in the text eliminated).</p>
73*/
74void FlavorPackageExternalizer::ExternalizeFlavor_TEXT (WriterFlavorPackage& flavorPackage, size_t from, size_t to)
75{
76 size_t start = from;
77 size_t end = to;
78 Require (start >= 0);
79 Require (end <= GetTextStore ().GetEnd ());
80 Require (start <= end);
81 size_t length = end - start;
82#if qStroika_Platform_Windows
83 Memory::StackBuffer<Led_tChar> buf{2 * length + 1}; //CRLF
84#else
86#endif
87 if (length != 0) {
89 GetTextStore ().CopyOut (start, length, buf2.data ());
90#if qStroika_Platform_Windows
91 length = Characters::NLToNative<Led_tChar> (buf2.data (), length, buf.data (), 2 * length + 1);
92#endif
93 }
94
95 // At least for MS-Windows - NUL-chars in the middle of the text just cause confusion in other
96 // apps. Don't know about Mac. But I doubt they help any there either. Since for this stuff
97 // we aren't going todo the right thing for sentinels ANYHOW - we may as well eliminate them
98 // (or any other NUL-chars)
99 length = Led_SkrunchOutSpecialChars (buf.data (), length, '\0');
100
101#if qStroika_Platform_Windows
102 buf[length] = '\0'; // Windows always expects CF_TEXT to be NUL char terminated
103 length++; // so AddFlavorData() writes out the NUL-byte
104#endif
105 flavorPackage.AddFlavorData (kTEXTClipFormat, length * sizeof (Led_tChar), buf.data ());
106}
107
108/*
109 ********************************************************************************
110 *************************** FlavorPackageInternalizer **************************
111 ********************************************************************************
112 */
113TextStore* FlavorPackageInternalizer::PeekAtTextStore () const
114{
115 return &fTextStore;
116}
117
118bool FlavorPackageInternalizer::InternalizeBestFlavor (ReaderFlavorPackage& flavorPackage, size_t from, size_t to)
119{
120 if (InternalizeFlavor_FILE (flavorPackage, from, to)) {
121 return true;
122 }
123 else if (InternalizeFlavor_TEXT (flavorPackage, from, to)) {
124 return true;
125 }
126 return false;
127}
128
129bool FlavorPackageInternalizer::InternalizeFlavor_TEXT (ReaderFlavorPackage& flavorPackage, size_t from, size_t to)
130{
131 if (flavorPackage.GetFlavorAvailable_TEXT ()) {
132 size_t length = flavorPackage.GetFlavorSize (kTEXTClipFormat);
133 Led_ClipFormat textFormat = kTEXTClipFormat;
134 Memory::StackBuffer<char> buf{length * sizeof (Led_tChar)}; // data read from flavor package is just an array of bytes (not Led_tChar)
135 // but allocate enuf space for converting TO UNICODE - in case of
136 // qWorkAroundWin95BrokenUNICODESupport workaround below - we may
137 // want to copy UNICODE chars in there instead.
138 length = flavorPackage.ReadFlavorData (textFormat, length, buf.data ());
139
140 Led_tChar* buffp = reinterpret_cast<Led_tChar*> (static_cast<char*> (buf)); // INTERPRET array of bytes as Led_tChars
141 size_t nTChars = length / sizeof (Led_tChar);
142#if qStroika_Platform_Windows
143 if (nTChars > 0) {
144 // On Windows - CF_TEXT always GUARANTEED to be NUL-terminated, and the
145 // length field is often wrong (rounded up to some chunk size, with garbage
146 // text at the end...
147 nTChars = min (nTChars, Led_tStrlen (buffp)); // do min in case clip data corrupt
148 }
149#endif
150
151 size_t start = from;
152 size_t end = to;
153 Require (start <= end);
154
155 nTChars = Characters::NormalizeTextToNL<Led_tChar> (buffp, nTChars, buffp, nTChars);
156 GetTextStore ().Replace (start, end, buffp, nTChars);
157 return true;
158 }
159 else {
160 return false;
161 }
162}
163
164bool FlavorPackageInternalizer::InternalizeFlavor_FILE (ReaderFlavorPackage& flavorPackage, size_t from, size_t to)
165{
166 // For now, we ignore any files beyond the first one (Mac&PC)...LGP 960522
167 if (flavorPackage.GetFlavorAvailable (kFILEClipFormat)) {
168 size_t fileSpecBufferLength = flavorPackage.GetFlavorSize (kFILEClipFormat);
169 Memory::StackBuffer<char> fileSpecBuffer{fileSpecBufferLength};
170 fileSpecBufferLength = flavorPackage.ReadFlavorData (kFILEClipFormat, fileSpecBufferLength, fileSpecBuffer.data ());
171
172// Unpack the filename
173#if qStroika_Platform_Windows
174 TCHAR realFileName[_MAX_PATH + 1];
175 {
176 HDROP hdrop = (HDROP)::GlobalAlloc (GMEM_FIXED, fileSpecBufferLength);
178 (void)::memcpy (hdrop, fileSpecBuffer.data (), fileSpecBufferLength);
179 size_t nChars = ::DragQueryFile (hdrop, 0, nullptr, 0);
180 Verify (::DragQueryFile (hdrop, 0, realFileName, static_cast<UINT> (nChars + 1)) == nChars);
181 ::GlobalFree (hdrop);
182 }
183 Led_ClipFormat suggestedClipFormat = kBadClipFormat; // no guess - examine text later
184#else
185 char realFileName[1000]; // use MAX_PATH? ??? NO - GET REAL SIZE !!! X-TMP-HACK-LGP991213
186 realFileName[0] = '\0';
187 Led_ClipFormat suggestedClipFormat = kBadClipFormat; // no guess - examine text later
188#endif
189
190 return InternalizeFlavor_FILEData (realFileName, &suggestedClipFormat, nullopt, from, to);
191 }
192 else {
193 return false;
194 }
195}
196
197bool FlavorPackageInternalizer::InternalizeFlavor_FILEData (filesystem::path fileName, Led_ClipFormat* suggestedClipFormat,
198 optional<CodePage> suggestedCodePage, size_t from, size_t to)
199{
200 Memory::BLOB b = IO::FileSystem::FileInputStream::New (filesystem::path (fileName)).ReadAll ();
201 const byte* fileBuf = b.begin ();
202 size_t fileLen = b.size ();
203
204 InternalizeFlavor_FILEGuessFormatsFromName (fileName, suggestedClipFormat, suggestedCodePage);
205
206 // If we still don't have a good guess of the clip format, then peek at the data, and
207 // try to guess...
208 InternalizeFlavor_FILEGuessFormatsFromStartOfData (suggestedClipFormat, suggestedCodePage, fileBuf, fileBuf + fileLen);
209
210 // By this point, we've read in the file data, and its stored in fileReader. And, we've been given guesses,
211 // and adjusted our guesses about the format (without looking at the data). Now just
212 // internalize the bytes according to our format guesses.
213 return InternalizeFlavor_FILEDataRawBytes (suggestedClipFormat, suggestedCodePage, from, to, fileBuf, fileLen);
214}
215
216void FlavorPackageInternalizer::InternalizeFlavor_FILEGuessFormatsFromName (filesystem::path fileName, Led_ClipFormat* suggestedClipFormat,
217 [[maybe_unused]] optional<CodePage> suggestedCodePage)
218{
219#if qStroika_Platform_Windows
220 if (suggestedClipFormat != nullptr and *suggestedClipFormat == kBadClipFormat) {
221 TCHAR drive[_MAX_DRIVE];
222 TCHAR dir[_MAX_DIR];
223 TCHAR fname[_MAX_FNAME];
224 TCHAR ext[_MAX_EXT];
225 ::_tsplitpath_s (fileName.native ().c_str (), drive, dir, fname, ext);
226 if (::_tcsicmp (ext, Led_SDK_TCHAROF (".txt")) == 0) {
227 *suggestedClipFormat = kTEXTClipFormat;
228 }
229 }
230#endif
231}
232
233void FlavorPackageInternalizer::InternalizeFlavor_FILEGuessFormatsFromStartOfData (Led_ClipFormat* suggestedClipFormat,
234 [[maybe_unused]] optional<CodePage> suggestedCodePage,
235 const byte* /*fileStart*/, const byte* /*fileEnd*/
236)
237{
238 if (suggestedClipFormat != nullptr) {
239 if (*suggestedClipFormat == kBadClipFormat) {
240 // Then try guessing some different formats. Since this module only knows of one, and thats a last ditch
241 // default - we don't venture a guess here. But - other formats - like RTF and HTML - have distinctive headers
242 // than can be scanned for to see if we are using that format data
243 }
244 }
245}
246
247bool FlavorPackageInternalizer::InternalizeFlavor_FILEDataRawBytes (Led_ClipFormat* suggestedClipFormat, optional<CodePage> suggestedCodePage,
248 size_t from, size_t to, const void* rawBytes, size_t nRawBytes)
249{
250 /*
251 * If suggesedFormat UNKNOWN - treat as text.
252 *
253 * If its TEXT, read it in here directly (using the suggested codePage).
254 *
255 * If its anything else - then create a temporary, fake memory-based SourcePackage, and re-internalize.
256 */
257 Led_ClipFormat cf = (suggestedClipFormat == nullptr or *suggestedClipFormat == kBadClipFormat) ? kTEXTClipFormat : *suggestedClipFormat;
258 if (cf != kTEXTClipFormat) {
260 package.AddFlavorData (cf, nRawBytes, rawBytes);
261 if (InternalizeBestFlavor (package, from, to)) {
262 return true;
263 }
264 }
265
266 /*
267 * If either the suggestedClipFormat was TEXT or it was something else we didn't understand - then just
268 * import that contents as if it was plain text
269 *
270 * We COULD just vector to InternalizeBestFlavor above for the TEXT case - but then we'd lose the passed information about the
271 * preferred code page - so just do the read/replace here...
272 */
273 span<const byte> rawByteSpan{reinterpret_cast<const byte*> (rawBytes), nRawBytes};
274#if qStroika_Platform_Windows
275 CodeCvt<Led_tChar> converter{&rawByteSpan, CodeCvt<Led_tChar>{suggestedCodePage.value_or (CP_ACP)}};
276#else
277 CodeCvt<Led_tChar> converter{&rawByteSpan, CodeCvt<Led_tChar>{locale{}}};
278#endif
279 size_t outCharCnt = converter.ComputeTargetCharacterBufferSize (rawByteSpan);
280 Memory::StackBuffer<Led_tChar> fileData2{outCharCnt};
281 auto charsRead = converter.Bytes2Characters (&rawByteSpan, span{fileData2}).size ();
282 charsRead = Characters::NormalizeTextToNL<Led_tChar> (fileData2.data (), charsRead, fileData2.data (), charsRead);
283 GetTextStore ().Replace (from, to, fileData2.data (), charsRead);
284 return true;
285}
286
287#if qStroika_Frameworks_Led_SupportClipboard
288/*
289 ********************************************************************************
290 *************************** ReaderClipboardFlavorPackage ***********************
291 ********************************************************************************
292 */
293
294bool ReaderClipboardFlavorPackage::GetFlavorAvailable (Led_ClipFormat clipFormat) const
295{
296 return Led_ClipboardObjectAcquire::FormatAvailable (clipFormat);
297}
298
299size_t ReaderClipboardFlavorPackage::GetFlavorSize (Led_ClipFormat clipFormat) const
300{
301 Led_ClipboardObjectAcquire clip (clipFormat);
302 if (clip.GoodClip ()) {
303 return clip.GetDataLength ();
304 }
305 else {
306 return 0;
307 }
308}
309
310size_t ReaderClipboardFlavorPackage::ReadFlavorData (Led_ClipFormat clipFormat, size_t bufSize, void* buf) const
311{
312 Led_ClipboardObjectAcquire clip{clipFormat};
313 if (clip.GoodClip ()) {
314 size_t copyNBytes = min (bufSize, clip.GetDataLength ());
315 (void)::memcpy (buf, clip.GetData (), copyNBytes);
316 Ensure (copyNBytes <= bufSize);
317 return copyNBytes;
318 }
319 else {
320 return 0;
321 }
322}
323#endif
324
325#if qStroika_Frameworks_Led_SupportClipboard
326/*
327 ********************************************************************************
328 **************************** WriterClipboardFlavorPackage **********************
329 ********************************************************************************
330 */
331void WriterClipboardFlavorPackage::AddFlavorData (Led_ClipFormat clipFormat, size_t bufSize, const void* buf)
332{
333#if qStroika_Platform_Windows
334 // NOTE: FOR THE PC - it is assumed all this happens in the context of an open/close clipboard
335 // done in the Led_MFC class overrides of OnCopyCommand_Before/OnCopyCommand_After
336 HANDLE h = ::GlobalAlloc (GHND | GMEM_MOVEABLE, bufSize);
338 {
339 char* clipBuf = (char*)::GlobalLock (h);
340 if (clipBuf == nullptr) {
341 ::GlobalFree (h);
342 Execution::Throw (bad_alloc{});
343 }
344 memcpy (clipBuf, buf, bufSize);
345 ::GlobalUnlock (h);
346 }
347 if (::SetClipboardData (clipFormat, h) == nullptr) {
348 DWORD err = ::GetLastError ();
349 ThrowIfErrorHRESULT (MAKE_HRESULT (SEVERITY_ERROR, FACILITY_WIN32, err));
350 }
351#endif
352}
353#endif
354
355/*
356 ********************************************************************************
357 ***************************** ReadWriteMemBufferPackage ************************
358 ********************************************************************************
359 */
360ReadWriteMemBufferPackage::ReadWriteMemBufferPackage ()
363 , fPackages{}
364{
365 // Tuned to the MWERKS CWPro1 STL implementation. If you don't call vector::reserve () it uses 361 (pagesize/size(T) apx)
366 // which means this is HUGE. And since we keep several of these (one per char typed for undo), it helps mem usage
367 // alot to keep this down.
368 //
369 // Even though tuned to Mac code, should work pretty well in any case.
370
371 //fPackages.SetSlotsAlloced (1);
372 // HAVENT TESTED WITH STLLIB - BUT Probably a good idea here too - LGP 980923
373 fPackages.reserve (1);
374}
375
376ReadWriteMemBufferPackage::~ReadWriteMemBufferPackage ()
377{
378}
379
380bool ReadWriteMemBufferPackage::GetFlavorAvailable (Led_ClipFormat clipFormat) const
381{
382 for (size_t i = 0; i < fPackages.size (); i++) {
383 if (fPackages[i].fFormat == clipFormat) {
384 return true;
385 }
386 }
387 return false;
388}
389
390size_t ReadWriteMemBufferPackage::GetFlavorSize (Led_ClipFormat clipFormat) const
391{
392 for (size_t i = 0; i < fPackages.size (); i++) {
393 if (fPackages[i].fFormat == clipFormat) {
394 return fPackages[i].fData.size ();
395 }
396 }
397 Assert (false);
398 return 0;
399}
400
401size_t ReadWriteMemBufferPackage::ReadFlavorData (Led_ClipFormat clipFormat, size_t bufSize, void* buf) const
402{
403 for (size_t i = 0; i < fPackages.size (); i++) {
404 if (fPackages[i].fFormat == clipFormat) {
405 size_t copyNBytes = min (bufSize, fPackages[i].fData.size ());
406 memcpy (buf, Traversal::Iterator2Pointer (fPackages[i].fData.begin ()), copyNBytes);
407 Ensure (copyNBytes <= bufSize);
408 return copyNBytes;
409 }
410 }
411 Assert (false);
412 return 0;
413}
414
415void ReadWriteMemBufferPackage::AddFlavorData (Led_ClipFormat clipFormat, size_t bufSize, const void* buf)
416{
417 PackageRecord pr;
418 pr.fFormat = clipFormat;
419 const char* cb = reinterpret_cast<const char*> (buf);
420 pr.fData = vector<char> (cb, cb + bufSize);
421 fPackages.push_back (pr);
422}
#define Verify(c)
Definition Assertions.h:422
CodeCvt unifies byte <-> unicode conversions, vaguely inspired by (and wraps) std::codecvt,...
Definition CodeCvt.h:118
nonvirtual const byte * begin() const
Definition BLOB.inl:258
nonvirtual size_t size() const
Definition BLOB.inl:286
Logically halfway between std::array and std::vector; Smart 'direct memory array' - which when needed...
virtual size_t GetFlavorSize(Led_ClipFormat clipFormat) const override
virtual size_t GetFlavorSize(Led_ClipFormat clipFormat) const =0
void Throw(T &&e2Throw)
like the builtin C++ 'throw', but stamps in the current Activity context and does helpful,...
Definition Throw.inl:54
void ThrowIfNull(const Private_::ConstVoidStar &p, const HRESULT &hr)
Template specialization for ThrowIfNull (), for thing being thrown HRESULT - really throw HRESULTErro...