1/*
2 * LegacyClonk
3 *
4 * Copyright (c) 1998-2000, Matthes Bender (RedWolf Design)
5 * Copyright (c) 2017-2022, The LegacyClonk Team and contributors
6 *
7 * Distributed under the terms of the ISC license; see accompanying file
8 * "COPYING" for details.
9 *
10 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
11 * See accompanying file "TRADEMARK" for details.
12 *
13 * To redistribute this file separately, substitute the full license texts
14 * for the above references.
15 */
16
17/* Holds a single text file component from a group */
18
19#include <C4ComponentHost.h>
20#include "C4Config.h"
21#include <C4Application.h>
22#include <C4Language.h>
23
24#ifdef _WIN32
25#include "C4Console.h"
26#include "StdRegistry.h"
27#include "StdStringEncodingConverter.h"
28#include "res/engine_resource.h"
29#endif
30
31C4ComponentHost::C4ComponentHost()
32{
33 Default();
34}
35
36C4ComponentHost::~C4ComponentHost()
37{
38 Clear();
39}
40
41void C4ComponentHost::Default()
42{
43 Data.Clear();
44 Modified = false;
45 Name[0] = 0;
46 Filename[0] = 0;
47 FilePath[0] = 0;
48}
49
50void C4ComponentHost::Clear()
51{
52 Data.Clear();
53}
54
55bool C4ComponentHost::Load(const char *szName,
56 C4Group &hGroup,
57 const char *szFilename,
58 const char *szLanguage)
59{
60 // Clear any old stuff
61 Clear();
62 // Store name & filename
63 SCopy(szSource: szName, sTarget: Name);
64 SCopy(szSource: szFilename, sTarget: Filename);
65 // Load component - try all segmented filenames
66 char strEntry[_MAX_FNAME + 1];
67 std::string entryWithLanguage;
68 for (int iFilename = 0; SCopySegment(fstr: Filename, segn: iFilename, tstr: strEntry, sepa: '|', _MAX_FNAME); iFilename++)
69 {
70 // Try to insert all language codes provided into the filename
71 char strCode[3] = "";
72 const char *const strCodePtr{strCode};
73 for (int iLang = 0; SCopySegment(fstr: szLanguage ? szLanguage : "", segn: iLang, tstr: strCode, sepa: ',', iMaxL: 2); iLang++)
74 {
75 // Insert language code
76 entryWithLanguage = std::vformat(fmt: strEntry, args: std::make_format_args(fmt_args: strCodePtr));
77 if (hGroup.LoadEntryString(szEntryName: entryWithLanguage.c_str(), Buf&: Data))
78 {
79 if (Config.General.fUTF8) Data.EnsureUnicode();
80 // Store actual filename
81 hGroup.FindEntry(szWildCard: entryWithLanguage.c_str(), sFileName: Filename);
82 CopyFilePathFromGroup(hGroup);
83 // Got it
84 return true;
85 }
86 // Couldn't insert language code anyway - no point in trying other languages
87 if (!SSearch(szString: strEntry, szIndex: "{}")) break;
88 }
89 }
90 // Truncate any additional segments from stored filename
91 SReplaceChar(str: Filename, fc: '|', tc: 0);
92 CopyFilePathFromGroup(hGroup);
93 // Not loaded
94 return false;
95}
96
97bool C4ComponentHost::Load(const char *szName,
98 C4GroupSet &hGroupSet,
99 const char *szFilename,
100 const char *szLanguage)
101{
102 // Clear any old stuff
103 Clear();
104 // Store name & filename
105 SCopy(szSource: szName, sTarget: Name);
106 SCopy(szSource: szFilename, sTarget: Filename);
107 // Load component - try all segmented filenames
108 char strEntry[_MAX_FNAME + 1];
109 std::string entryWithLanguage;
110 for (int iFilename = 0; SCopySegment(fstr: Filename, segn: iFilename, tstr: strEntry, sepa: '|', _MAX_FNAME); iFilename++)
111 {
112 // Try to insert all language codes provided into the filename
113 char strCode[3] = "";
114 const char *const strCodePtr{strCode};
115 for (int iLang = 0; SCopySegment(fstr: szLanguage ? szLanguage : "", segn: iLang, tstr: strCode, sepa: ',', iMaxL: 2); iLang++)
116 {
117 // Insert language code
118 entryWithLanguage = std::vformat(fmt: strEntry, args: std::make_format_args(fmt_args: strCodePtr));
119 if (hGroupSet.LoadEntryString(szEntryName: entryWithLanguage.c_str(), rBuf&: Data))
120 {
121 if (Config.General.fUTF8) Data.EnsureUnicode();
122 // Store actual filename
123 C4Group *pGroup = hGroupSet.FindEntry(szWildcard: entryWithLanguage.c_str());
124 pGroup->FindEntry(szWildCard: entryWithLanguage.c_str(), sFileName: Filename);
125 CopyFilePathFromGroup(hGroup: *pGroup);
126 // Got it
127 return true;
128 }
129 // Couldn't insert language code anyway - no point in trying other languages
130 if (!SSearch(szString: strEntry, szIndex: "{}")) break;
131 }
132 }
133 // Truncate any additional segments from stored filename
134 SReplaceChar(str: Filename, fc: '|', tc: 0);
135 // skip full path (unknown)
136 FilePath[0] = 0;
137 // Not loaded
138 return false;
139}
140
141bool C4ComponentHost::LoadEx(const char *szName,
142 C4Group &hGroup,
143 const char *szFilename,
144 const char *szLanguage)
145{
146 // Load from a group set containing the provided group and
147 // alternative groups for cross-loading from a language pack
148 C4GroupSet hGroups;
149 hGroups.RegisterGroup(rGroup&: hGroup, fOwnGrp: false, Priority: 1000, C4GSCnt_Component); // Provided group gets highest priority
150 hGroups.RegisterGroups(rCopy&: Languages.GetPackGroups(strRelativePath: Config.AtExeRelativePath(szFilename: hGroup.GetFullName().getData())), C4GSCnt_Language);
151 // Load from group set
152 return Load(szName, hGroupSet&: hGroups, szFilename, szLanguage);
153}
154
155bool C4ComponentHost::LoadAppend(const char *szName,
156 C4Group &hGroup, const char *szFilename,
157 const char *szLanguage)
158{
159 Clear();
160
161 // Store name & filename
162 SCopy(szSource: szName, sTarget: Name);
163 SCopy(szSource: szFilename, sTarget: Filename);
164
165 // Load component (segmented filename)
166 char str1[_MAX_FNAME + 1];
167 std::string entry;
168 size_t iFileCnt = 0, iFileSizeSum = 0;
169 for (size_t cseg = 0; SCopySegment(fstr: Filename, segn: cseg, tstr: str1, sepa: '|', _MAX_FNAME); cseg++)
170 {
171 char szLang[3] = "";
172 const char *const szLangPtr{szLang};
173 for (size_t clseg = 0; SCopySegment(fstr: szLanguage ? szLanguage : "", segn: clseg, tstr: szLang, sepa: ',', iMaxL: 2); clseg++)
174 {
175 entry = std::vformat(fmt: str1, args: std::make_format_args(fmt_args: szLangPtr));
176 // Check existance
177 size_t iFileSize;
178 if (hGroup.FindEntry(szWildCard: entry.c_str(), sFileName: nullptr, iSize: &iFileSize))
179 {
180 iFileCnt++;
181 iFileSizeSum += 1 + iFileSize;
182 break;
183 }
184 if (!SSearch(szString: str1, szIndex: "{}")) break;
185 }
186 }
187
188 // No matching files found?
189 if (!iFileCnt) return false;
190
191 // Allocate memory
192 Data.SetLength(iFileSizeSum);
193
194 // Search files to read contents
195 char *pPos = Data.getMData(); *pPos = 0;
196 for (size_t cseg = 0; SCopySegment(fstr: Filename, segn: cseg, tstr: str1, sepa: '|', _MAX_FNAME); cseg++)
197 {
198 char szLang[3] = "";
199 const char *const szLangPtr{szLang};
200 for (size_t clseg = 0; SCopySegment(fstr: szLanguage ? szLanguage : "", segn: clseg, tstr: szLang, sepa: ',', iMaxL: 2); clseg++)
201 {
202 entry = std::vformat(fmt: str1, args: std::make_format_args(fmt_args: szLangPtr));
203 // Load data
204 char *pTemp;
205 if (hGroup.LoadEntry(szEntryName: entry.c_str(), lpbpBuf: &pTemp, ipSize: nullptr, iAppendZeros: 1))
206 {
207 *pPos++ = '\n';
208 SCopy(szSource: pTemp, sTarget: pPos, iMaxL: Data.getPtr(i: Data.getLength()) - pPos);
209 pPos += SLen(sptr: pPos);
210 delete[] pTemp;
211 break;
212 }
213 delete[] pTemp;
214 if (!SSearch(szString: str1, szIndex: "{}")) break;
215 }
216 }
217
218 SReplaceChar(str: Filename, fc: '|', tc: 0);
219 CopyFilePathFromGroup(hGroup);
220 return !!iFileCnt;
221}
222
223// Construct full path
224void C4ComponentHost::CopyFilePathFromGroup(const C4Group &hGroup)
225{
226 SCopy(szSource: Config.AtExeRelativePath(szFilename: hGroup.GetFullName().getData()), sTarget: FilePath, _MAX_PATH - 1);
227 SAppendChar(DirectorySeparator, szStr: FilePath);
228 SAppend(szSource: Filename, szTarget: FilePath, _MAX_PATH);
229}
230
231bool C4ComponentHost::Save(C4Group &hGroup)
232{
233 if (!Modified) return true;
234 if (!Data) return hGroup.Delete(szFiles: Filename);
235 return hGroup.Add(szName: Filename, pBuffer&: Data);
236}
237
238bool C4ComponentHost::GetLanguageString(const char *szLanguage, StdStrBuf &rTarget)
239{
240 const char *cptr;
241 // No good parameters
242 if (!szLanguage || !Data) return false;
243 // Search for two-letter language identifier in text body, i.e. "DE:"
244 char langindex[4] = "";
245 for (int clseg = 0; SCopySegment(fstr: szLanguage ? szLanguage : "", segn: clseg, tstr: langindex, sepa: ',', iMaxL: 2); clseg++)
246 {
247 SAppend(szSource: ":", szTarget: langindex);
248 if (cptr = SSearch(szString: Data.getData(), szIndex: langindex))
249 {
250 // Return the according string
251 auto iEndPos = SCharPos(cTarget: '\r', szInStr: cptr);
252 if (iEndPos < 0) iEndPos = SCharPos(cTarget: '\n', szInStr: cptr);
253 if (iEndPos < 0) iEndPos = strlen(s: cptr);
254 rTarget.Copy(pnData: cptr, iChars: iEndPos);
255 return true;
256 }
257 }
258 // Language string not found
259 return false;
260}
261
262void C4ComponentHost::Close()
263{
264}
265
266void C4ComponentHost::TrimSpaces()
267{
268 Data.TrimSpaces();
269 Modified = true;
270}
271
272#ifdef _WIN32
273INT_PTR CALLBACK C4ComponentHost::ComponentDlgProc(const HWND hDlg, const UINT msg, const WPARAM wParam, const LPARAM lParam)
274{
275 const auto close = [hDlg](C4ComponentHost &host)
276 {
277 host.Close();
278 EndDialog(hDlg, 1);
279 };
280
281 switch (msg)
282 {
283 case WM_INITDIALOG:
284 {
285 const auto &componentHost = *reinterpret_cast<C4ComponentHost *>(lParam);
286
287 SetWindowLongPtr(hDlg, DWLP_USER, lParam);
288 SetWindowText(hDlg, StdStringEncodingConverter::WinAcpToUtf16(componentHost.Name).c_str());
289 SetDlgItemText(hDlg, IDOK, StdStringEncodingConverter::WinAcpToUtf16(LoadResStr(C4ResStrTableKey::IDS_BTN_OK)).c_str());
290 SetDlgItemText(hDlg, IDCANCEL, StdStringEncodingConverter::WinAcpToUtf16(LoadResStr(C4ResStrTableKey::IDS_BTN_CANCEL)).c_str());
291
292 if (const char *const data{componentHost.GetData()}; data)
293 {
294 SetDlgItemText(hDlg, IDC_EDITDATA, StdStringEncodingConverter::WinAcpToUtf16(data).c_str());
295 }
296
297 RestoreWindowPosition(hDlg, "Component", Config.GetSubkeyPath("Console"));
298
299 return TRUE;
300 }
301
302 case WM_CLOSE:
303 close(*reinterpret_cast<C4ComponentHost *>(GetWindowLongPtr(hDlg, DWLP_USER)));
304 return 0;
305
306 case WM_DESTROY:
307 StoreWindowPosition(hDlg, "Component", Config.GetSubkeyPath("Console"), false);
308 return 0;
309
310 case WM_COMMAND:
311 {
312 auto &componentHost = *reinterpret_cast<C4ComponentHost *>(GetWindowLongPtr(hDlg, DWLP_USER));
313 switch (LOWORD(wParam))
314 {
315 case IDCANCEL:
316 close(componentHost);
317 return TRUE;
318
319 case IDOK:
320 {
321 StdStrBuf &data{componentHost.Data};
322 const auto size = static_cast<std::size_t>(SendDlgItemMessage(hDlg, IDC_EDITDATA, WM_GETTEXTLENGTH, 0, 0));
323 if (size == 0)
324 {
325 data.Clear();
326 }
327 else
328 {
329 const std::string text{StdStringEncodingConverter::Utf16ToWinAcp(C4Console::GetDialogItemText(hDlg, IDC_EDITDATA))};
330 componentHost.Data.Copy(text.c_str(), text.size());
331 }
332
333 componentHost.Modified = true;
334 close(componentHost);
335
336 return TRUE;
337 }
338 }
339
340 return FALSE;
341 }
342 }
343
344 return FALSE;
345}
346
347void C4ComponentHost::ShowDialog(const HWND parent)
348{
349 DialogBoxParam(Application.hInstance, MAKEINTRESOURCE(IDD_COMPONENT), parent, &ComponentDlgProc, reinterpret_cast<LPARAM>(this));
350}
351#endif
352