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